mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-04-28 15:55:28 +00:00
commit
ddba33ffc7
17 changed files with 759 additions and 989 deletions
96
common/src/main/scala/net/psforever/objects/ObjectType.scala
Normal file
96
common/src/main/scala/net/psforever/objects/ObjectType.scala
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright (c) 2017 PSForever
|
||||||
|
package net.psforever.objects
|
||||||
|
|
||||||
|
object ObjectType extends Enumeration {
|
||||||
|
type Value = String
|
||||||
|
|
||||||
|
val AmbientSoundSource = "ambient_sound_source"
|
||||||
|
val Ammunition = "ammunition"
|
||||||
|
val AnimatedBarrier = "animated_barrier"
|
||||||
|
val Applicator = "applicator"
|
||||||
|
val Armor = "armor"
|
||||||
|
val ArmorSiphon = "armor_siphon"
|
||||||
|
val AwardStatistic = "award_statistic"
|
||||||
|
val Avatar = "avatar"
|
||||||
|
val AvatarBot = "avatar_bot"
|
||||||
|
val Ball = "ball"
|
||||||
|
val Bank = "bank"
|
||||||
|
val Barrier = "barrier"
|
||||||
|
val BfrTerminal = "bfr_terminal"
|
||||||
|
val Billboard = "billboard"
|
||||||
|
val Boomer = "boomer"
|
||||||
|
val BoomerTrigger = "boomer_trigger"
|
||||||
|
val Building = "building"
|
||||||
|
val CaptureFlag = "capture_flag"
|
||||||
|
val CaptureFlagSocket = "capture_flag_socket"
|
||||||
|
val CaptureTerminal = "capture_terminal"
|
||||||
|
val CertTerminal = "cert_terminal"
|
||||||
|
val ChainLashDamager = "chain_lash_damager"
|
||||||
|
val Dispenser = "dispenser"
|
||||||
|
val Door = "door"
|
||||||
|
val EmpBlast = "emp_blast"
|
||||||
|
val FrameVehicle = "framevehicle"
|
||||||
|
val Flag = "flag"
|
||||||
|
val FlightVehicle = "flightvehicle"
|
||||||
|
val ForceDome = "forcedome"
|
||||||
|
val ForceDomeGenerator = "forcedomegenerator"
|
||||||
|
val Game = "game"
|
||||||
|
val Generic = "generic"
|
||||||
|
val GenericTeleportion = "generic_teleportation"
|
||||||
|
val GeneratorTerminal = "generator_terminal"
|
||||||
|
val GsGenbase = "GS_genbase"
|
||||||
|
val HandGrenade = "hand_grenade"
|
||||||
|
val HeMine = "he_mine"
|
||||||
|
val HeavyWeapon = "heavy_weapon"
|
||||||
|
val HoverVehicle = "hovervehicle"
|
||||||
|
val Implant = "implant"
|
||||||
|
val ImplantInterfaceTerminal = "implant_terminal_interface"
|
||||||
|
val Lazer = "lazer"
|
||||||
|
val Locker = "locker"
|
||||||
|
val LockerContainer = "locker_container"
|
||||||
|
val LockExternal = "lock_external"
|
||||||
|
val LockSmall = "lock_small"
|
||||||
|
val MainTerminal = "main_terminal"
|
||||||
|
val Map = "map"
|
||||||
|
val MedicalTerminal = "medical_terminal"
|
||||||
|
val Medkit = "medkit"
|
||||||
|
val Monolith = "monolith"
|
||||||
|
val MonolithUnit = "monolith_unit"
|
||||||
|
val MotionAlarmSensorDest = "motion_alarm_sensor_dest"
|
||||||
|
val NanoDispenser = "nano_dispenser"
|
||||||
|
val NtuSipon = "ntu_siphon"
|
||||||
|
val OrbitalShuttlePad = "orbital_shuttle_pad"
|
||||||
|
val OrbitalStrike = "orbital_strike"
|
||||||
|
val OrderTerminal = "order_terminal"
|
||||||
|
val PainTerminal = "pain_terminal"
|
||||||
|
val Projectile = "projectile"
|
||||||
|
val RadiationCloud = "radiation_cloud"
|
||||||
|
val RearmTerminal = "rearm_terminal"
|
||||||
|
val RechargeTerminal = "recharge_terminal"
|
||||||
|
val Rek = "rek"
|
||||||
|
val RepairTerminal = "repair_terminal"
|
||||||
|
val ResourceSilo = "resource_silo"
|
||||||
|
val RespawnTube = "respawn_tube"
|
||||||
|
val SensorShield = "sensor_shield"
|
||||||
|
val ShieldGenerator = "shield_generator"
|
||||||
|
val Shifter = "shifter"
|
||||||
|
val SkyDome = "skydome"
|
||||||
|
val SpawnPlayer = "spawn_player"
|
||||||
|
val SpawnPoint = "spawn_point"
|
||||||
|
val SpawnTerminal = "spawn_terminal"
|
||||||
|
val TeleportPad = "teleport_pad"
|
||||||
|
val Terminal = "terminal"
|
||||||
|
val TradeContainer = "trade_container"
|
||||||
|
val UplinkDevice = "uplink_device"
|
||||||
|
val VanuCradleClass = "vanu_cradle_class"
|
||||||
|
val VanuModuleClass = "vanu_module_class"
|
||||||
|
val VanuModuleFactory = "vanu_module_factory"
|
||||||
|
val VanuReceptacleClass = "vanu_receptacle_class"
|
||||||
|
val Vehicle = "vehicle"
|
||||||
|
val VehicleCreationPad = "vehicle_creation_pad"
|
||||||
|
val VehicleLandingPad = "vehicle_landing_pad"
|
||||||
|
val VehicleTerminal = "vehicle_terminal"
|
||||||
|
val Warpgate = "waprgate"
|
||||||
|
val WarpZone = "warp_zone"
|
||||||
|
val Weapon = "weapon"
|
||||||
|
}
|
||||||
|
|
@ -60,14 +60,18 @@ class NumberPoolHub(private val source : NumberSource) {
|
||||||
* @param name the name of the pool
|
* @param name the name of the pool
|
||||||
* @param pool the `List` of numbers that will belong to the pool
|
* @param pool the `List` of numbers that will belong to the pool
|
||||||
* @return the newly-created number pool
|
* @return the newly-created number pool
|
||||||
* @throws IllegalArgumentException if the pool is already defined;
|
* @throws IllegalArgumentException if the pool's name is already defined;
|
||||||
* if the pool contains numbers the source does not
|
* if the pool is (already) empty;
|
||||||
|
* if the pool contains numbers the source does not;
|
||||||
* if the pool contains numbers from already existing pools
|
* if the pool contains numbers from already existing pools
|
||||||
*/
|
*/
|
||||||
def AddPool(name : String, pool : List[Int]) : NumberPool = {
|
def AddPool(name : String, pool : List[Int]) : NumberPool = {
|
||||||
if(hash.get(name).isDefined) {
|
if(hash.get(name).isDefined) {
|
||||||
throw new IllegalArgumentException(s"can not add pool $name - name already known to this hub?")
|
throw new IllegalArgumentException(s"can not add pool $name - name already known to this hub?")
|
||||||
}
|
}
|
||||||
|
if(pool.isEmpty) {
|
||||||
|
throw new IllegalArgumentException(s"can not add empty pool $name")
|
||||||
|
}
|
||||||
if(source.Size <= pool.max) {
|
if(source.Size <= pool.max) {
|
||||||
throw new IllegalArgumentException(s"can not add pool $name - max(pool) is greater than source.size")
|
throw new IllegalArgumentException(s"can not add pool $name - max(pool) is greater than source.size")
|
||||||
}
|
}
|
||||||
|
|
@ -203,8 +207,8 @@ class NumberPoolHub(private val source : NumberSource) {
|
||||||
val slctr = pool.Selector
|
val slctr = pool.Selector
|
||||||
import net.psforever.objects.guid.selector.SpecificSelector
|
import net.psforever.objects.guid.selector.SpecificSelector
|
||||||
val specific = new SpecificSelector
|
val specific = new SpecificSelector
|
||||||
specific.SelectionIndex = number
|
|
||||||
pool.Selector = specific
|
pool.Selector = specific
|
||||||
|
specific.SelectionIndex = number
|
||||||
pool.Get()
|
pool.Get()
|
||||||
pool.Selector = slctr
|
pool.Selector = slctr
|
||||||
register_GetAvailableNumberFromSource(number)
|
register_GetAvailableNumberFromSource(number)
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid.actor
|
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A message for requesting information about the registration status of an object or a number.
|
|
||||||
* @param obj the optional object
|
|
||||||
* @param number the optional number
|
|
||||||
*/
|
|
||||||
final case class IsRegistered(obj : Option[IdentifiableEntity], number : Option[Int])
|
|
||||||
|
|
||||||
object IsRegistered {
|
|
||||||
/**
|
|
||||||
* Overloaded constructor for querying an object's status.
|
|
||||||
* @param obj the object
|
|
||||||
* @return an `IsRegistered` object
|
|
||||||
*/
|
|
||||||
def apply(obj : IdentifiableEntity) : IsRegistered = {
|
|
||||||
new IsRegistered(Some(obj), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overloaded constructor for querying a number's status.
|
|
||||||
* @param number the number
|
|
||||||
* @return an `IsRegistered` object
|
|
||||||
*/
|
|
||||||
def apply(number : Int) : IsRegistered = {
|
|
||||||
new IsRegistered(None, Some(number))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,216 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid.actor
|
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef}
|
|
||||||
import akka.pattern.ask
|
|
||||||
import akka.util.Timeout
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
import net.psforever.objects.guid.NumberPoolHub
|
|
||||||
import net.psforever.objects.guid.pool.NumberPool
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import scala.util.{Failure, Success}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An `Actor` that wraps around the `Actor` for a `NumberPool` and automates a portion of the number registration process.<br>
|
|
||||||
* <br>
|
|
||||||
* The `NumberPoolActor` that is created is used as the synchronized "gate" through which the number selection process occurs.
|
|
||||||
* This `Actor` `ask`s the internal `Actor` and then waits on that `Future` to resolve.
|
|
||||||
* For the registration process, once it resolves, a number for the accompanying object has been chosen.
|
|
||||||
* The last part involves configuring the `NumberSource` of the hub so that it knows.
|
|
||||||
* For the process of revoking registration, the number from the object is returned to the pool.
|
|
||||||
* Like during the registration process, the `NumberSource` is then also updated.<br>
|
|
||||||
* <br>
|
|
||||||
* The object is always registered using the underlying governed `NumberPool`.
|
|
||||||
* The object will not unregister if the object or its number are not recognized as members previously registered to the `NumberPool`.<br>
|
|
||||||
* Whether or not an object or a specific number has been registered is always possible.
|
|
||||||
* The scope encompasses the whole of the associated `NumberSource` as opposed to just this `NumberPool`.
|
|
||||||
* @param hub the `NumberPoolHub` this `Actor` manipulates
|
|
||||||
* @param pool the specific `NumberPool` this `Actor` maintains
|
|
||||||
* @param poolActor a shared `Actor` that governs this `NumberPool`
|
|
||||||
*/
|
|
||||||
class NumberPoolAccessorActor(private val hub : NumberPoolHub, private val pool : NumberPool, private val poolActor : ActorRef) extends Actor {
|
|
||||||
//the timeout is for when we ask the poolActor
|
|
||||||
private implicit val timeout = Timeout(50 milliseconds)
|
|
||||||
private[this] val log = org.log4s.getLogger
|
|
||||||
|
|
||||||
private final case class GUIDRequest(obj : IdentifiableEntity, replyTo : ActorRef)
|
|
||||||
private val requestQueue : collection.mutable.LongMap[GUIDRequest] = new collection.mutable.LongMap()
|
|
||||||
private var index : Long = Long.MinValue
|
|
||||||
|
|
||||||
def receive : Receive = {
|
|
||||||
//register
|
|
||||||
case Register(obj, _, None, call) =>
|
|
||||||
try {
|
|
||||||
obj.GUID //stop if object has a GUID; sometimes this happens
|
|
||||||
log.warn(s"$obj already registered")
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
case _ : Exception =>
|
|
||||||
val id : Long = index
|
|
||||||
index += 1
|
|
||||||
requestQueue += id -> GUIDRequest(obj, call.getOrElse(sender()))
|
|
||||||
poolActor ! NumberPoolActor.GetAnyNumber(Some(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
case Register(obj, _, Some(number), call) =>
|
|
||||||
try {
|
|
||||||
obj.GUID //stop if object has a GUID; sometimes this happens
|
|
||||||
log.warn(s"$obj already registered")
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
case _ : Exception =>
|
|
||||||
val id : Long = index
|
|
||||||
index += 1
|
|
||||||
requestQueue += id -> GUIDRequest(obj, call.getOrElse(sender()))
|
|
||||||
poolActor ! NumberPoolActor.GetSpecificNumber(number, Some(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
case NumberPoolActor.GiveNumber(number, id) =>
|
|
||||||
id match {
|
|
||||||
case Some(nid : Long) =>
|
|
||||||
Register(nid, requestQueue.remove(nid), number)
|
|
||||||
case _ =>
|
|
||||||
pool.Return(number) //recovery?
|
|
||||||
log.warn(s"received a number but there is no request to process it; returning number to pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
case NumberPoolActor.NoNumber(ex, id) =>
|
|
||||||
val req = id match {
|
|
||||||
case Some(nid : Long) =>
|
|
||||||
val req = requestQueue.remove(nid)
|
|
||||||
if(req.isDefined) { s"$req" } else { s"a corresponding request $nid was not found;" }
|
|
||||||
case _ =>
|
|
||||||
"generic request;" //should be unreachable
|
|
||||||
}
|
|
||||||
log.warn(s"a number was not drawn from the pool; $req $ex")
|
|
||||||
|
|
||||||
//unregister
|
|
||||||
case Unregister(obj, call) =>
|
|
||||||
val callback = call.getOrElse(sender())
|
|
||||||
try {
|
|
||||||
val number = obj.GUID.guid
|
|
||||||
if(pool.Numbers.contains(number) && hub.WhichPool(obj).isDefined) {
|
|
||||||
val id : Long = index
|
|
||||||
index += 1
|
|
||||||
requestQueue += id -> GUIDRequest(obj, callback)
|
|
||||||
poolActor ! NumberPoolActor.ReturnNumber(number, Some(id))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
callback ! Failure(new Exception(s"the GUID of object $obj - $number - is not a part of this number pool"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
case msg : Exception =>
|
|
||||||
callback ! Failure(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
case NumberPoolActor.ReturnNumberResult(number, None, id) =>
|
|
||||||
id match {
|
|
||||||
case Some(nid : Long) =>
|
|
||||||
Unregister(nid, requestQueue.remove(nid), number)
|
|
||||||
case _ =>
|
|
||||||
NumberPoolActor.GetSpecificNumber(pool, number) //recovery?
|
|
||||||
log.error(s"returned a number but there is no request to process it; recovering the number from pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
case NumberPoolActor.ReturnNumberResult(number, ex, id) =>
|
|
||||||
val req = id match {
|
|
||||||
case Some(nid : Long) =>
|
|
||||||
val req = requestQueue.remove(nid)
|
|
||||||
if(req.isDefined) { s"$req" } else { s"a corresponding request $nid was not found;" }
|
|
||||||
case _ =>
|
|
||||||
"generic request;" //should be unreachable
|
|
||||||
}
|
|
||||||
log.warn(s"a number $number was not returned to the pool; $req $ex")
|
|
||||||
|
|
||||||
//common
|
|
||||||
case IsRegistered(Some(obj), None) =>
|
|
||||||
sender ! hub.isRegistered(obj)
|
|
||||||
|
|
||||||
case IsRegistered(None, Some(number)) =>
|
|
||||||
sender ! hub.isRegistered(number)
|
|
||||||
|
|
||||||
case NumberPoolActor.ReturnNumber(number, _) =>
|
|
||||||
sender ! (poolActor ? NumberPoolActor.ReturnNumber(number))
|
|
||||||
|
|
||||||
case msg =>
|
|
||||||
log.warn(s"unexpected message received - $msg")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object registration process.
|
|
||||||
* If there is a successful request object to be found, complete the registration request.
|
|
||||||
* @param id the identifier of this request
|
|
||||||
* @param request the request data
|
|
||||||
* @param number the number that was drawn from the `NumberPool`
|
|
||||||
*/
|
|
||||||
private def Register(id : Long, request : Option[GUIDRequest], number : Int) : Unit = {
|
|
||||||
request match {
|
|
||||||
case Some(GUIDRequest(obj, replyTo)) =>
|
|
||||||
processRegisterResult(obj, number, replyTo)
|
|
||||||
case None =>
|
|
||||||
pool.Return(number) //recovery?
|
|
||||||
log.warn(s"received a number but the request for it is missing; returning number to pool")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object registration process.
|
|
||||||
* This step completes the registration by consulting the `NumberSource`.
|
|
||||||
* @param obj the object
|
|
||||||
* @param number the number to use
|
|
||||||
* @param callback an optional callback `ActorRef`
|
|
||||||
*/
|
|
||||||
private def processRegisterResult(obj : IdentifiableEntity, number : Int, callback : ActorRef) : Unit = {
|
|
||||||
try {
|
|
||||||
obj.GUID
|
|
||||||
pool.Return(number) //recovery?
|
|
||||||
callback ! Success(obj)
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
case _ : Exception =>
|
|
||||||
hub.latterPartRegister(obj, number) match {
|
|
||||||
case Success(_) =>
|
|
||||||
callback ! Success(obj)
|
|
||||||
case Failure(ex) =>
|
|
||||||
pool.Return(number) //recovery?
|
|
||||||
callback ! Failure(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object un-registration process.
|
|
||||||
* If there is a successful request object to be found, complete the registration request.
|
|
||||||
* @param id the identifier of this request
|
|
||||||
* @param request the request data
|
|
||||||
* @param number the number that was drawn from the `NumberPool`
|
|
||||||
*/
|
|
||||||
private def Unregister(id : Long, request : Option[GUIDRequest], number : Int) : Unit = {
|
|
||||||
request match {
|
|
||||||
case Some(GUIDRequest(obj, replyTo)) =>
|
|
||||||
processUnregisterResult(obj, obj.GUID.guid, replyTo)
|
|
||||||
case None =>
|
|
||||||
NumberPoolActor.GetSpecificNumber(pool, number) //recovery?
|
|
||||||
log.error(s"returned a number but the rest of the request is missing; recovering the number from pool")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object un-registration process.
|
|
||||||
* This step completes revoking the object's registration by consulting the `NumberSource`.
|
|
||||||
* @param obj the object
|
|
||||||
* @param callback an optional callback `ActorRef`
|
|
||||||
*/
|
|
||||||
private def processUnregisterResult(obj : IdentifiableEntity, number : Int, callback : ActorRef) : Unit = {
|
|
||||||
hub.latterPartUnregister(number) match {
|
|
||||||
case Some(_) =>
|
|
||||||
obj.Invalidate()
|
|
||||||
callback ! Success(obj)
|
|
||||||
case None =>
|
|
||||||
NumberPoolActor.GetSpecificNumber(pool, number) //recovery?
|
|
||||||
callback ! Failure(new Exception(s"failed to unregister a number; this may be a critical error"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid.actor
|
|
||||||
|
|
||||||
import akka.pattern.ask
|
|
||||||
import akka.util.Timeout
|
|
||||||
import akka.actor.{Actor, ActorRef, Props}
|
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
import net.psforever.objects.guid.NumberPoolHub
|
|
||||||
import net.psforever.objects.guid.pool.NumberPool
|
|
||||||
|
|
||||||
import scala.collection.mutable
|
|
||||||
import scala.concurrent.Future
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import scala.util.{Failure, Success, Try}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An incoming message for retrieving a specific `NumberPoolAccessorActor`.
|
|
||||||
* @param name the name of the accessor's `NumberPool`
|
|
||||||
*/
|
|
||||||
final case class RequestPoolActor(name : String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An outgoing message for giving a specific `NumberPoolAccessorActor`.
|
|
||||||
* @param name the name of the accessor's `NumberPool`, for reference
|
|
||||||
* @param actor the accessor
|
|
||||||
*/
|
|
||||||
final case class DeliverPoolActor(name : String, actor : ActorRef)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An `Actor` that wraps around the management system for `NumberPools`.<br>
|
|
||||||
* <br>
|
|
||||||
* By just instantiating, this object builds and stores a `NumberPoolAccessorActor` for each `NumberPool` known to the `hub`.
|
|
||||||
* Additional `NumberPool`s created by the `hub` need to be paired with a created accessor manually.
|
|
||||||
* Each accessor is the primary entry point to a registration process for the specific `NumberPool` it represents.
|
|
||||||
* The `hub` `Actor` itself distribute any registration task it receives out to an applicable accessor of which it is aware.
|
|
||||||
* It will attempt to revoke registration on its own, without relying on the functionality from any accessor.<br>
|
|
||||||
* <br>
|
|
||||||
* In the same way that `NumberPoolHub` is a tool for keeping track of `NumberPool` objects,
|
|
||||||
* its `Actor` is a tool for keeping track of accessors created from `NumberPool` objects.
|
|
||||||
* It is very, however, for handling unspecific revoke tasks.
|
|
||||||
* @param hub the central `NumberPool` management object for an embedded `NumberSource` object
|
|
||||||
*/
|
|
||||||
class NumberPoolHubActor(private val hub : NumberPoolHub) extends Actor {
|
|
||||||
private val actorHash : mutable.HashMap[String, ActorRef] = mutable.HashMap[String, ActorRef]()
|
|
||||||
hub.Pools.foreach({ case(name, pool) => CreatePoolActor(name, pool) })
|
|
||||||
implicit val timeout = Timeout(50 milliseconds)
|
|
||||||
private[this] val log = org.log4s.getLogger
|
|
||||||
|
|
||||||
def receive : Receive = {
|
|
||||||
case RequestPoolActor(name) =>
|
|
||||||
sender ! (GetPoolActor(name) match {
|
|
||||||
case Success(poolActor) =>
|
|
||||||
DeliverPoolActor(name, poolActor)
|
|
||||||
case Failure(ex) =>
|
|
||||||
Failure(ex)
|
|
||||||
})
|
|
||||||
|
|
||||||
case Register(obj, name, None, callback) =>
|
|
||||||
HubRegister(obj, name, callback)
|
|
||||||
|
|
||||||
case Register(obj, name, Some(number), callback) =>
|
|
||||||
HubRegister(obj, name, number, callback)
|
|
||||||
|
|
||||||
//common
|
|
||||||
case IsRegistered(Some(obj), None) =>
|
|
||||||
sender ! hub.isRegistered(obj)
|
|
||||||
|
|
||||||
case IsRegistered(None, Some(number)) =>
|
|
||||||
sender ! hub.isRegistered(number)
|
|
||||||
|
|
||||||
case Unregister(obj, callback) =>
|
|
||||||
Unregister(obj, if(callback.isEmpty) { sender } else { callback.get })
|
|
||||||
|
|
||||||
case msg =>
|
|
||||||
log.warn(s"unexpected message received - ${msg.toString}")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* From a name, find an existing `NumberPoolAccessorActor`.
|
|
||||||
* @param name the accessor's name
|
|
||||||
* @return the accessor that was requested
|
|
||||||
*/
|
|
||||||
private def GetPoolActor(name : String) : Try[ActorRef] = {
|
|
||||||
actorHash.get(name) match {
|
|
||||||
case Some(actor) =>
|
|
||||||
Success(actor)
|
|
||||||
case _ =>
|
|
||||||
Failure(new Exception(s"number pool $name not defined"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new `NumberPoolAccessorActor` and add it to the local collection of accessors.
|
|
||||||
* @param name the accessor's name
|
|
||||||
* @param pool the underlying `NumberPool`
|
|
||||||
*/
|
|
||||||
private def CreatePoolActor(name : String, pool : NumberPool) : Unit = {
|
|
||||||
actorHash.get(name) match {
|
|
||||||
case None =>
|
|
||||||
actorHash += name -> context.actorOf(Props(classOf[NumberPoolAccessorActor], hub, pool), s"${name}Actor")
|
|
||||||
case Some(_) =>
|
|
||||||
//TODO complain?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object registration process.
|
|
||||||
* Select a valid `NumberPoolAccessorActor` and pass a task onto it.
|
|
||||||
* @param obj an object
|
|
||||||
* @param name a potential accessor pool
|
|
||||||
* @param callback an optional callback `ActorRef`
|
|
||||||
*/
|
|
||||||
private def HubRegister(obj : IdentifiableEntity, name : Option[String], callback : Option[ActorRef]) : Unit = {
|
|
||||||
val genericPool = actorHash("generic")
|
|
||||||
val pool = if(name.isDefined) { actorHash.get(name.get).orElse(Some(genericPool)).get } else { genericPool }
|
|
||||||
pool ! Register(obj, None, None, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object registration process.
|
|
||||||
* Determine to which `NumberPool` the `number` belongs.
|
|
||||||
* @param obj an object
|
|
||||||
* @param name a potential accessor pool
|
|
||||||
* @param number a potential number
|
|
||||||
* @param callback an optional callback `ActorRef`
|
|
||||||
*/
|
|
||||||
private def HubRegister(obj : IdentifiableEntity, name : Option[String], number : Int, callback : Option[ActorRef]) : Unit = {
|
|
||||||
hub.WhichPool(number) match {
|
|
||||||
case Some(poolname) =>
|
|
||||||
HubRegister_GetActor(obj, name, poolname, number, callback)
|
|
||||||
case None =>
|
|
||||||
self ! Register(obj, name, None, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object registration process.
|
|
||||||
* Pass a task onto an accessor or, if the accessor can not be found, attempt to recover.
|
|
||||||
* @param obj an object
|
|
||||||
* @param name a potential accessor pool
|
|
||||||
* @param poolname the suggested accessor pool
|
|
||||||
* @param number a potential number
|
|
||||||
* @param callback an optional callback `ActorRef`
|
|
||||||
*/
|
|
||||||
private def HubRegister_GetActor(obj : IdentifiableEntity, name : Option[String], poolname : String, number : Int, callback : Option[ActorRef]) : Unit = {
|
|
||||||
actorHash.get(poolname) match {
|
|
||||||
case Some(pool) =>
|
|
||||||
pool ! Register(obj, None, Some(number), callback)
|
|
||||||
case None =>
|
|
||||||
HubRegister_MissingActor(obj, name, poolname, number, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object registration process.
|
|
||||||
* If an accessor could not be found in the last step, attempt to create the accessor.
|
|
||||||
* If the accessor can not be created, the `number` can not be used;
|
|
||||||
* fall back on the original pool (`name`).
|
|
||||||
* @param obj an object
|
|
||||||
* @param name a potential accessor pool
|
|
||||||
* @param poolname the suggested accessor pool
|
|
||||||
* @param number a potential number
|
|
||||||
* @param callback an optional callback `ActorRef`
|
|
||||||
*/
|
|
||||||
private def HubRegister_MissingActor(obj : IdentifiableEntity, name : Option[String], poolname : String, number : Int, callback : Option[ActorRef]) : Unit = {
|
|
||||||
hub.GetPool(poolname) match {
|
|
||||||
case Some(pool) =>
|
|
||||||
CreatePoolActor(poolname, pool)
|
|
||||||
actorHash(poolname) ! Register(obj, None, Some(number), callback)
|
|
||||||
case None =>
|
|
||||||
log.error(s"matched number $number to pool $poolname, but could not find $poolname when asked")
|
|
||||||
self ! Register(obj, name, None, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object un-registration process.
|
|
||||||
* This step locates the `NumberPool` to which this object is a member.
|
|
||||||
* If found, it prepares a `Future` to resolve later regarding whether the `NumberPool` accepted the number.
|
|
||||||
* @param obj the object
|
|
||||||
* @param callback a callback `ActorRef`
|
|
||||||
*/
|
|
||||||
private def Unregister(obj : IdentifiableEntity, callback : ActorRef) : Unit = {
|
|
||||||
hub.WhichPool(obj) match {
|
|
||||||
case Some(name) =>
|
|
||||||
val objToUnregister = obj
|
|
||||||
val poolName = name
|
|
||||||
processUnregisterResult(objToUnregister, (actorHash(poolName) ? NumberPoolActor.ReturnNumber(objToUnregister.GUID.guid)).mapTo[Boolean], callback)
|
|
||||||
case None =>
|
|
||||||
callback ! UnregisterFailure(obj, new Exception("could not find pool object is member of"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object un-registration process.
|
|
||||||
* This step completes revoking the object's registration by consulting the `NumberSource`.
|
|
||||||
* @param obj the object
|
|
||||||
* @param result whether the number was returned in the last step
|
|
||||||
* @param callback a callback `ActorRef`
|
|
||||||
*/
|
|
||||||
private def processUnregisterResult(obj : IdentifiableEntity, result : Future[Boolean], callback : ActorRef) : Unit = {
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
|
||||||
result.foreach {
|
|
||||||
case true =>
|
|
||||||
hub.latterPartUnregister(obj.GUID.guid)
|
|
||||||
callback ! UnregisterSuccess(obj)
|
|
||||||
case false =>
|
|
||||||
callback ! UnregisterFailure(obj, new Exception("could not find object to remove"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,348 @@
|
||||||
|
// Copyright (c) 2017 PSForever
|
||||||
|
package net.psforever.objects.guid.actor
|
||||||
|
|
||||||
|
import akka.actor.{Actor, ActorContext, ActorRef, Props}
|
||||||
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
|
import net.psforever.objects.guid.NumberPoolHub
|
||||||
|
|
||||||
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An `Actor` that wraps around converted `NumberPool`s and synchronizes a portion of the number registration process.
|
||||||
|
* The ultimate goal is to manage a coherent group of unique identifiers for a given "region" (`Zone`).
|
||||||
|
* Both parts of the UID system sit atop the `Zone` for easy external access.
|
||||||
|
* The plain part - the `NumberPoolHub` here - is used for low-priority requests such as checking for existing associations.
|
||||||
|
* This `Actor` is the involved portion that paces registration and unregistration.<br>
|
||||||
|
* <br>
|
||||||
|
* A four part process is used for object registration tasks.
|
||||||
|
* First, the requested `NumberPool` is located among the list of known `NumberPool`s.
|
||||||
|
* Second, an asynchronous request is sent to that pool to retrieve a number.
|
||||||
|
* (Only any number. Only a failing case allows for selection of a specific number.)
|
||||||
|
* Third, the asynchronous request returns and the original information about the request is recovered.
|
||||||
|
* Fourth, both sides of the contract are completed by the object being assigned the number and
|
||||||
|
* the underlying "number source" is made to remember an association between the object and the number.
|
||||||
|
* Short circuits and recoveries as available on all steps though reporting is split between logging and callbacks.
|
||||||
|
* The process of removing the association between a number and object (unregistering) is a similar four part process.<br>
|
||||||
|
* <br>
|
||||||
|
* The important relationship between this `Actor` and the `Map` of `NumberPoolActors` is an "gate."
|
||||||
|
* A single `Map` is constructed and shared between multiple entry points to the UID system where requests are messaged.
|
||||||
|
* Multiple entry points send messages to the same `NumberPool`.
|
||||||
|
* That `NumberPool` deals with the messages one at a time and sends reply to each entry point that communicated with it.
|
||||||
|
* This process is almost as fast as the process of the `NumberPool` selecting a number.
|
||||||
|
* (At least, both should be fast.)
|
||||||
|
* @param guid the `NumberPoolHub` that is partially manipulated by this `Actor`
|
||||||
|
* @param poolActors a common mapping created from the `NumberPool`s in `guid`;
|
||||||
|
* there is currently no check for this condition save for requests failing
|
||||||
|
*/
|
||||||
|
class UniqueNumberSystem(private val guid : NumberPoolHub, private val poolActors : Map[String, ActorRef]) extends Actor {
|
||||||
|
/** Information about Register and Unregister requests that persists between messages to a specific `NumberPool`. */
|
||||||
|
private val requestQueue : collection.mutable.LongMap[UniqueNumberSystem.GUIDRequest] = new collection.mutable.LongMap()
|
||||||
|
/** The current value for the next request entry's index. */
|
||||||
|
private var index : Long = Long.MinValue
|
||||||
|
private[this] val log = org.log4s.getLogger
|
||||||
|
|
||||||
|
def receive : Receive = {
|
||||||
|
case Register(obj, Some(pname), None, call) =>
|
||||||
|
val callback = call.getOrElse(sender())
|
||||||
|
try {
|
||||||
|
obj.GUID //stop if object already has a GUID; sometimes this happens
|
||||||
|
AlreadyRegistered(obj, pname)
|
||||||
|
callback ! Success(obj)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
case _ : Exception =>
|
||||||
|
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.info(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.get(poolName) match {
|
||||||
|
case Some(pool) =>
|
||||||
|
pool ! NumberPoolActor.ReturnNumber(number, Some(Long.MinValue))
|
||||||
|
case None =>
|
||||||
|
log.error(s"critical: tried to return number $number but did not find pool $poolName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.get(poolName) match {
|
||||||
|
case Some(pool) =>
|
||||||
|
pool ! NumberPoolActor.GetSpecificNumber(number, Some(Long.MinValue))
|
||||||
|
case None =>
|
||||||
|
log.error(s"critical: tried to re-register number $number but did not find pool $poolName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object UniqueNumberSystem {
|
||||||
|
/**
|
||||||
|
* Persistent record of the important information between the time fo request and the time of reply.
|
||||||
|
* @param target the object
|
||||||
|
* @param targetPool the name of the `NumberPool` being used
|
||||||
|
* @param replyTo the callback `ActorRef`
|
||||||
|
*/
|
||||||
|
private final case class GUIDRequest(target : IdentifiableEntity, targetPool : String, replyTo : ActorRef)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform `NumberPool`s into `NumberPoolActor`s and pair them with their name.
|
||||||
|
* @param poolSource where the raw `NumberPools` are located
|
||||||
|
* @param context used to create the `NumberPoolActor` instances
|
||||||
|
* @return a `Map` of the pool names to the `ActorRef` created from the `NumberPool`
|
||||||
|
*/
|
||||||
|
def AllocateNumberPoolActors(poolSource : NumberPoolHub)(implicit context : ActorContext) : Map[String, ActorRef] = {
|
||||||
|
poolSource.Pools.map({ case ((pname, pool)) =>
|
||||||
|
pname -> context.actorOf(Props(classOf[NumberPoolActor], pool), pname)
|
||||||
|
}).toMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid.actor
|
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A message for when an object has failed to be unregistered for some reason.
|
|
||||||
* @param obj the object
|
|
||||||
* @param ex the reason that the registration process failed
|
|
||||||
*/
|
|
||||||
final case class UnregisterFailure(obj : IdentifiableEntity, ex : Throwable)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid.actor
|
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A message for when an object has been unregistered.
|
|
||||||
* @param obj the object
|
|
||||||
*/
|
|
||||||
final case class UnregisterSuccess(obj : IdentifiableEntity)
|
|
||||||
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid.misc
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is just a proof of concept model of a self-contained system.
|
|
||||||
*/
|
|
||||||
class AscendingNumberSource {
|
|
||||||
val pool : Array[Int] = Array.ofDim[Int](65536)
|
|
||||||
(0 to 65535).foreach(x => { pool(x) = x })
|
|
||||||
var head : Int = 0
|
|
||||||
|
|
||||||
def Get() : Int = {
|
|
||||||
val start : Int = head
|
|
||||||
if(pool(head) == -1) {
|
|
||||||
do {
|
|
||||||
head = (head + 1) % pool.length
|
|
||||||
}
|
|
||||||
while(pool(head) == -1 && head != start)
|
|
||||||
}
|
|
||||||
if(head == start) {
|
|
||||||
import net.psforever.objects.entity.NoGUIDException
|
|
||||||
throw NoGUIDException("no unused numbers available")
|
|
||||||
}
|
|
||||||
val outNumber : Int = head
|
|
||||||
pool(head) = -1
|
|
||||||
outNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
def Return(number : Int) : Unit = {
|
|
||||||
pool(number) = number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid.misc
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, Cancellable}
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import scala.util.{Failure, Success, Try}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept a task in waiting and series of lesser tasks that complete the provided primary task.
|
|
||||||
* Receive periodic updates on the states of the lesser tasks and, when these sub-tasks have been accomplished,
|
|
||||||
* declare the primary task accomplished as well.<br>
|
|
||||||
* <br>
|
|
||||||
* This ia admittedly a simplistic model of task resolution, currently, and is rather specific and limited.
|
|
||||||
* Generalizing and expanding on this class in the future might be beneficial.
|
|
||||||
* @param obj the primary task
|
|
||||||
* @param list a series of sub-tasks that need to be completed before the pimrary task can be completed
|
|
||||||
* @param callback where to report about the pirmary task having succeeded or failed
|
|
||||||
* @param timeoutDuration a delay during which sub-tasks are permitted to be accomplished;
|
|
||||||
* after this grave period is over, the task has failed
|
|
||||||
*/
|
|
||||||
class RegistrationTaskResolver[T <: IdentifiableEntity](private val obj : T, private val list : List[T], callback : ActorRef, timeoutDuration : FiniteDuration) extends Actor {
|
|
||||||
/** sub-tasks that contribute to completion of the task */
|
|
||||||
private val checklist : Array[Boolean] = Array.fill[Boolean](list.length)(false)
|
|
||||||
/** whether or not it matters that sub-tasks are coming in */
|
|
||||||
private var valid : Boolean = true
|
|
||||||
/** declares when the task has taken too long to complete */
|
|
||||||
private val taskTimeout : Cancellable = context.system.scheduler.scheduleOnce(timeoutDuration, self, Failure(new TimeoutException(s"a task for $obj has timed out")))
|
|
||||||
private[this] val log = org.log4s.getLogger
|
|
||||||
ConfirmTask(Success(true)) //check for auto-completion
|
|
||||||
|
|
||||||
def receive : Receive = {
|
|
||||||
case Success(objn)=>
|
|
||||||
ConfirmTask(ConfirmSubtask(objn.asInstanceOf[T]))
|
|
||||||
|
|
||||||
case Failure(ex)=>
|
|
||||||
FailedTask(ex)
|
|
||||||
|
|
||||||
case msg =>
|
|
||||||
log.warn(s"unexpected message received - ${msg.toString}")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this object is still accepting task resolutions, determine if that sub-task can be checked off.
|
|
||||||
* @param objn the sub-task entry
|
|
||||||
* @return a successful pass or a failure if the task can't be found;
|
|
||||||
* a "successful failure" if task resolutions are no longer accepted
|
|
||||||
*/
|
|
||||||
private def ConfirmSubtask(objn : T) : Try[Boolean] = {
|
|
||||||
if(valid) {
|
|
||||||
if(MatchSubtask(objn, list.iterator)) {
|
|
||||||
Success(true)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Failure(new Exception(s"can not find a subtask to check off - ${objn.toString}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Success(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a sub-task from a `List` of sub-tasks and mark it as completed, if found.
|
|
||||||
* @param objn the sub-task entry
|
|
||||||
* @param iter_list an `Iterator` to the list of sub-tasks
|
|
||||||
* @param index the index of this entry;
|
|
||||||
* defaults to zero
|
|
||||||
* @return whether or not the subtask has been marked as completed
|
|
||||||
*/
|
|
||||||
@tailrec private def MatchSubtask(objn : T, iter_list : Iterator[T], index : Int = 0) : Boolean = {
|
|
||||||
if(!iter_list.hasNext) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val subtask = iter_list.next
|
|
||||||
if(subtask.equals(objn)) {
|
|
||||||
checklist(index) = true
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
MatchSubtask(objn, iter_list, index + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine whether all sub-tasks have been completed successfully.
|
|
||||||
* If so, complete the primary task.
|
|
||||||
* @param subtaskComplete the status of the recent sub-task confirmation that triggered this confirmation request
|
|
||||||
*/
|
|
||||||
private def ConfirmTask(subtaskComplete : Try[Boolean]) : Unit = {
|
|
||||||
if(valid) {
|
|
||||||
subtaskComplete match {
|
|
||||||
case Success(true) =>
|
|
||||||
if(!checklist.contains(false)) {
|
|
||||||
FulfillTask()
|
|
||||||
}
|
|
||||||
case Success(false) =>
|
|
||||||
log.warn(s"when checking a task for ${obj.toString}, arrived at a state where we previously failed a subtask but main task still valid")
|
|
||||||
case Failure(ex) =>
|
|
||||||
FailedTask(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All sub-tasks have been completed; the main task can also be completed.
|
|
||||||
* Alert interested parties that the task is performed successfully.
|
|
||||||
* Stop as soon as possible.
|
|
||||||
*/
|
|
||||||
private def FulfillTask() : Unit = {
|
|
||||||
valid = false
|
|
||||||
callback ! Success(obj)
|
|
||||||
taskTimeout.cancel()
|
|
||||||
context.stop(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main task can not be completed.
|
|
||||||
* Clean up as much as possible and alert interested parties that the task has been dropped.
|
|
||||||
* Let this `Actor` stop gracefully.
|
|
||||||
* @param ex why the main task can not be completed
|
|
||||||
*/
|
|
||||||
private def FailedTask(ex : Throwable) : Unit = {
|
|
||||||
valid = false
|
|
||||||
callback ! Failure(ex)
|
|
||||||
taskTimeout.cancel()
|
|
||||||
import akka.pattern.gracefulStop
|
|
||||||
gracefulStop(self, 2 seconds) //give time for any other messages; avoid dead letters
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
|
|
||||||
* The numbers are considered to be exclusive.<br>
|
|
||||||
* <br>
|
|
||||||
* This source utilizes all positive integers (to `Int.MaxValue`, anyway) and zero.
|
|
||||||
* It allocates number `Monitors` as it needs them.
|
|
||||||
* While this allows for a wide range of possible numbers, the internal structure expands and contracts as needed.
|
|
||||||
* The underlying flexible structure is a `LongMap` and is subject to constraints regarding `LongMap` growth.
|
|
||||||
*/
|
|
||||||
class MaxNumberSource() extends NumberSource {
|
|
||||||
import scala.collection.mutable
|
|
||||||
private val hash : mutable.LongMap[Key] = mutable.LongMap[Key]() //TODO consider seeding an initialBufferSize
|
|
||||||
private var allowRestrictions : Boolean = true
|
|
||||||
|
|
||||||
def Size : Int = Int.MaxValue
|
|
||||||
|
|
||||||
def CountAvailable : Int = Size - CountUsed
|
|
||||||
|
|
||||||
def CountUsed : Int = hash.size
|
|
||||||
|
|
||||||
override def Test(guid : Int) : Boolean = guid > -1
|
|
||||||
|
|
||||||
def Get(number : Int) : Option[SecureKey] = {
|
|
||||||
if(!Test(number)) {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val existing : Option[Key] = hash.get(number).orElse({
|
|
||||||
val key : Key = new Key
|
|
||||||
key.Policy = AvailabilityPolicy.Available
|
|
||||||
hash.put(number, key)
|
|
||||||
Some(key)
|
|
||||||
})
|
|
||||||
Some(new SecureKey(number, existing.get))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// def GetAll(list : List[Int]) : List[SecureKey] = {
|
|
||||||
// list.map(number =>
|
|
||||||
// hash.get(number) match {
|
|
||||||
// case Some(key) =>
|
|
||||||
// new SecureKey(number, key)
|
|
||||||
// case _ =>
|
|
||||||
// new SecureKey(number, new Key { Policy = AvailabilityPolicy.Available })
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// def GetAll( p : Key => Boolean ) : List[SecureKey] = {
|
|
||||||
// hash.filter(entry => p.apply(entry._2)).map(entry => new SecureKey(entry._1.toInt, entry._2)).toList
|
|
||||||
// }
|
|
||||||
|
|
||||||
def Available(number : Int) : Option[LoanedKey] = {
|
|
||||||
if(!Test(number)) {
|
|
||||||
throw new IndexOutOfBoundsException("number can not be negative")
|
|
||||||
}
|
|
||||||
hash.get(number) match {
|
|
||||||
case Some(_) =>
|
|
||||||
None
|
|
||||||
case _ =>
|
|
||||||
val key : Key = new Key
|
|
||||||
key.Policy = AvailabilityPolicy.Leased
|
|
||||||
hash.put(number, key)
|
|
||||||
Some(new LoanedKey(number, key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Return(number : Int) : Option[IdentifiableEntity] = {
|
|
||||||
val existing = hash.get(number)
|
|
||||||
if(existing.isDefined && existing.get.Policy == AvailabilityPolicy.Leased) {
|
|
||||||
hash -= number
|
|
||||||
val obj = existing.get.Object
|
|
||||||
existing.get.Object = None
|
|
||||||
obj
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Restrict(number : Int) : Option[LoanedKey] = {
|
|
||||||
if(allowRestrictions) {
|
|
||||||
val existing : Key = hash.get(number).orElse({
|
|
||||||
val key : Key = new Key
|
|
||||||
hash.put(number, key)
|
|
||||||
Some(key)
|
|
||||||
}).get
|
|
||||||
existing.Policy = AvailabilityPolicy.Restricted
|
|
||||||
Some(new LoanedKey(number, existing))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def FinalizeRestrictions : List[Int] = {
|
|
||||||
allowRestrictions = false
|
|
||||||
hash.filter(entry => entry._2.Policy == AvailabilityPolicy.Restricted).map(entry => entry._1.toInt).toList
|
|
||||||
}
|
|
||||||
|
|
||||||
def Clear() : List[IdentifiableEntity] = {
|
|
||||||
val list : List[IdentifiableEntity] = hash.values.filter(key => key.Object.isDefined).map(key => key.Object.get).toList
|
|
||||||
hash.clear()
|
|
||||||
list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object MaxNumberSource {
|
|
||||||
def apply() : MaxNumberSource = {
|
|
||||||
new MaxNumberSource()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@ import akka.actor.{ActorContext, ActorRef, Props}
|
||||||
import net.psforever.objects.{PlanetSideGameObject, Player}
|
import net.psforever.objects.{PlanetSideGameObject, Player}
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.objects.guid.NumberPoolHub
|
import net.psforever.objects.guid.NumberPoolHub
|
||||||
import net.psforever.objects.guid.actor.{NumberPoolAccessorActor, NumberPoolActor}
|
import net.psforever.objects.guid.actor.UniqueNumberSystem
|
||||||
import net.psforever.objects.guid.selector.RandomSelector
|
import net.psforever.objects.guid.selector.RandomSelector
|
||||||
import net.psforever.objects.guid.source.LimitedNumberSource
|
import net.psforever.objects.guid.source.LimitedNumberSource
|
||||||
import net.psforever.packet.GamePacket
|
import net.psforever.packet.GamePacket
|
||||||
|
|
@ -41,6 +41,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
|
||||||
private var accessor : ActorRef = ActorRef.noSender
|
private var accessor : ActorRef = ActorRef.noSender
|
||||||
/** The basic support structure for the globally unique number system used by this `Zone`. */
|
/** The basic support structure for the globally unique number system used by this `Zone`. */
|
||||||
private var guid : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(65536))
|
private var guid : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(65536))
|
||||||
|
guid.AddPool("environment", (0 to 2000).toList)
|
||||||
|
guid.AddPool("dynamic", (2001 to 10000).toList).Selector = new RandomSelector //TODO unlump pools later; do not make too big
|
||||||
/** A synchronized `List` of items (`Equipment`) dropped by players on the ground and can be collected again. */
|
/** A synchronized `List` of items (`Equipment`) dropped by players on the ground and can be collected again. */
|
||||||
private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]()
|
private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]()
|
||||||
/** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */
|
/** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */
|
||||||
|
|
@ -60,13 +62,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
|
||||||
*/
|
*/
|
||||||
def Init(implicit context : ActorContext) : Unit = {
|
def Init(implicit context : ActorContext) : Unit = {
|
||||||
if(accessor == ActorRef.noSender) {
|
if(accessor == ActorRef.noSender) {
|
||||||
//TODO wrong initialization for GUID
|
implicit val guid : NumberPoolHub = this.guid //passed into builderObject.Build implicitly
|
||||||
implicit val guid = this.guid
|
accessor = context.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid)), s"$Id-uns")
|
||||||
//passed into builderObject.Build implicitly
|
|
||||||
val pool = guid.AddPool("pool", (200 to 1000).toList)
|
|
||||||
pool.Selector = new RandomSelector
|
|
||||||
val poolActor = context.actorOf(Props(classOf[NumberPoolActor], pool), name = s"$Id-poolActor")
|
|
||||||
accessor = context.actorOf(Props(classOf[NumberPoolAccessorActor], guid, pool, poolActor), s"$Id-accessor")
|
|
||||||
ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground")
|
ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground")
|
||||||
|
|
||||||
Map.LocalObjects.foreach({ builderObject =>
|
Map.LocalObjects.foreach({ builderObject =>
|
||||||
|
|
|
||||||
13
common/src/test/scala/objects/ActorTest.scala
Normal file
13
common/src/test/scala/objects/ActorTest.scala
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright (c) 2017 PSForever
|
||||||
|
package objects
|
||||||
|
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.testkit.{ImplicitSender, TestKit}
|
||||||
|
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
|
||||||
|
import org.specs2.specification.Scope
|
||||||
|
|
||||||
|
abstract class ActorTest(sys : ActorSystem) extends TestKit(sys) with Scope with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll {
|
||||||
|
override def afterAll {
|
||||||
|
TestKit.shutdownActorSystem(system)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,24 +2,11 @@
|
||||||
package objects
|
package objects
|
||||||
|
|
||||||
import akka.actor.{ActorSystem, Props}
|
import akka.actor.{ActorSystem, Props}
|
||||||
import akka.testkit.{ImplicitSender, TestKit, TestProbe}
|
import net.psforever.objects.guid.actor.NumberPoolActor
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
import net.psforever.objects.guid.NumberPoolHub
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
|
|
||||||
import net.psforever.objects.guid.actor.{NumberPoolAccessorActor, NumberPoolActor, Register}
|
|
||||||
import net.psforever.objects.guid.pool.ExclusivePool
|
import net.psforever.objects.guid.pool.ExclusivePool
|
||||||
import net.psforever.objects.guid.selector.RandomSelector
|
import net.psforever.objects.guid.selector.RandomSelector
|
||||||
import net.psforever.objects.guid.source.LimitedNumberSource
|
|
||||||
import org.specs2.specification.Scope
|
|
||||||
|
|
||||||
import scala.concurrent.duration.Duration
|
import scala.concurrent.duration.Duration
|
||||||
import scala.util.Success
|
|
||||||
|
|
||||||
abstract class ActorTest(sys : ActorSystem) extends TestKit(sys) with Scope with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll {
|
|
||||||
override def afterAll {
|
|
||||||
TestKit.shutdownActorSystem(system)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NumberPoolActorTest extends ActorTest(ActorSystem("test")) {
|
class NumberPoolActorTest extends ActorTest(ActorSystem("test")) {
|
||||||
"NumberPoolActor" should {
|
"NumberPoolActor" should {
|
||||||
|
|
@ -61,23 +48,3 @@ class NumberPoolActorTest2 extends ActorTest(ActorSystem("test")) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NumberPoolActorTest3 extends ActorTest(ActorSystem("test")) {
|
|
||||||
"NumberPoolAccessorActor" should {
|
|
||||||
class TestEntity extends IdentifiableEntity
|
|
||||||
|
|
||||||
"register" in {
|
|
||||||
val hub = new NumberPoolHub(new LimitedNumberSource(51))
|
|
||||||
val pool = hub.AddPool("test", (25 to 50).toList)
|
|
||||||
pool.Selector = new RandomSelector
|
|
||||||
val poolActor = system.actorOf(Props(classOf[NumberPoolActor], pool), name = "poolActor")
|
|
||||||
val poolAccessor = system.actorOf(Props(classOf[NumberPoolAccessorActor], hub, pool, poolActor), name = "accessor")
|
|
||||||
|
|
||||||
val obj : TestEntity = new TestEntity
|
|
||||||
val probe = new TestProbe(system)
|
|
||||||
poolAccessor ! Register(obj, probe.ref)
|
|
||||||
probe.expectMsg(Success(obj))
|
|
||||||
assert({obj.GUID; true}) //NoGUIDException if failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -9,180 +9,6 @@ class NumberSourceTest extends Specification {
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
private class TestClass extends IdentifiableEntity
|
private class TestClass extends IdentifiableEntity
|
||||||
|
|
||||||
"MaxNumberSource" should {
|
|
||||||
import net.psforever.objects.guid.source.MaxNumberSource
|
|
||||||
"construct" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
obj.Size mustEqual Int.MaxValue
|
|
||||||
obj.CountAvailable mustEqual Int.MaxValue
|
|
||||||
obj.CountUsed mustEqual 0
|
|
||||||
}
|
|
||||||
|
|
||||||
"get a number" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val result : Option[LoanedKey] = obj.Available(5)
|
|
||||||
result.isDefined mustEqual true
|
|
||||||
result.get.GUID mustEqual 5
|
|
||||||
result.get.Policy mustEqual AvailabilityPolicy.Leased
|
|
||||||
result.get.Object mustEqual None
|
|
||||||
obj.Size mustEqual Int.MaxValue
|
|
||||||
obj.CountAvailable mustEqual Int.MaxValue - 1
|
|
||||||
obj.CountUsed mustEqual 1
|
|
||||||
}
|
|
||||||
|
|
||||||
"assign the number" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val result : Option[LoanedKey] = obj.Available(5)
|
|
||||||
result.isDefined mustEqual true
|
|
||||||
result.get.Object = new TestClass()
|
|
||||||
ok
|
|
||||||
}
|
|
||||||
|
|
||||||
"return a number (unused)" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val result : Option[LoanedKey] = obj.Available(5)
|
|
||||||
result.isDefined mustEqual true
|
|
||||||
result.get.GUID mustEqual 5
|
|
||||||
obj.CountUsed mustEqual 1
|
|
||||||
val ret = obj.Return(result.get)
|
|
||||||
ret mustEqual None
|
|
||||||
obj.CountUsed mustEqual 0
|
|
||||||
}
|
|
||||||
|
|
||||||
"return a number (assigned)" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val test = new TestClass()
|
|
||||||
val result : Option[LoanedKey] = obj.Available(5)
|
|
||||||
result.isDefined mustEqual true
|
|
||||||
result.get.GUID mustEqual 5
|
|
||||||
result.get.Object = test
|
|
||||||
obj.CountUsed mustEqual 1
|
|
||||||
val ret = obj.Return(result.get)
|
|
||||||
ret mustEqual Some(test)
|
|
||||||
obj.CountUsed mustEqual 0
|
|
||||||
}
|
|
||||||
|
|
||||||
"restrict a number (unassigned)" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val result : Option[LoanedKey] = obj.Restrict(5)
|
|
||||||
result.isDefined mustEqual true
|
|
||||||
result.get.GUID mustEqual 5
|
|
||||||
result.get.Policy mustEqual AvailabilityPolicy.Restricted
|
|
||||||
result.get.Object mustEqual None
|
|
||||||
}
|
|
||||||
|
|
||||||
"restrict a number (assigned + multiple assignments)" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val test1 = new TestClass()
|
|
||||||
val test2 = new TestClass()
|
|
||||||
val result : Option[LoanedKey] = obj.Restrict(5)
|
|
||||||
result.get.GUID mustEqual 5
|
|
||||||
result.get.Policy mustEqual AvailabilityPolicy.Restricted
|
|
||||||
result.get.Object mustEqual None
|
|
||||||
result.get.Object = None //assignment 1
|
|
||||||
result.get.Object mustEqual None //still unassigned
|
|
||||||
result.get.Object = test1 //assignment 2
|
|
||||||
result.get.Object mustEqual Some(test1)
|
|
||||||
result.get.Object = test2 //assignment 3
|
|
||||||
result.get.Object mustEqual Some(test1) //same as above
|
|
||||||
}
|
|
||||||
|
|
||||||
"return a restricted number (correctly fail)" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val test = new TestClass()
|
|
||||||
val result : Option[LoanedKey] = obj.Restrict(5)
|
|
||||||
result.get.GUID mustEqual 5
|
|
||||||
result.get.Policy mustEqual AvailabilityPolicy.Restricted
|
|
||||||
result.get.Object = test
|
|
||||||
|
|
||||||
obj.Return(5)
|
|
||||||
val result2 : Option[SecureKey] = obj.Get(5)
|
|
||||||
result2.get.GUID mustEqual 5
|
|
||||||
result2.get.Policy mustEqual AvailabilityPolicy.Restricted
|
|
||||||
result2.get.Object mustEqual Some(test)
|
|
||||||
}
|
|
||||||
|
|
||||||
"restrict a previously-assigned number" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val test = new TestClass()
|
|
||||||
val result1 : Option[LoanedKey] = obj.Available(5)
|
|
||||||
result1.isDefined mustEqual true
|
|
||||||
result1.get.Policy mustEqual AvailabilityPolicy.Leased
|
|
||||||
result1.get.Object = test
|
|
||||||
val result2 : Option[LoanedKey] = obj.Restrict(5)
|
|
||||||
result2.isDefined mustEqual true
|
|
||||||
result2.get.Policy mustEqual AvailabilityPolicy.Restricted
|
|
||||||
result2.get.Object mustEqual Some(test)
|
|
||||||
}
|
|
||||||
|
|
||||||
"check a number (not previously gotten)" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val result2 : Option[SecureKey] = obj.Get(5)
|
|
||||||
result2.get.GUID mustEqual 5
|
|
||||||
result2.get.Policy mustEqual AvailabilityPolicy.Available
|
|
||||||
result2.get.Object mustEqual None
|
|
||||||
}
|
|
||||||
|
|
||||||
"check a number (previously gotten)" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val result : Option[LoanedKey] = obj.Available(5)
|
|
||||||
result.isDefined mustEqual true
|
|
||||||
result.get.GUID mustEqual 5
|
|
||||||
result.get.Policy mustEqual AvailabilityPolicy.Leased
|
|
||||||
result.get.Object mustEqual None
|
|
||||||
val result2 : Option[SecureKey] = obj.Get(5)
|
|
||||||
result2.get.GUID mustEqual 5
|
|
||||||
result2.get.Policy mustEqual AvailabilityPolicy.Leased
|
|
||||||
result2.get.Object mustEqual None
|
|
||||||
}
|
|
||||||
|
|
||||||
"check a number (assigned)" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val result : Option[LoanedKey] = obj.Available(5)
|
|
||||||
result.isDefined mustEqual true
|
|
||||||
result.get.GUID mustEqual 5
|
|
||||||
result.get.Policy mustEqual AvailabilityPolicy.Leased
|
|
||||||
result.get.Object = new TestClass()
|
|
||||||
val result2 : Option[SecureKey] = obj.Get(5)
|
|
||||||
result2.get.GUID mustEqual 5
|
|
||||||
result2.get.Policy mustEqual AvailabilityPolicy.Leased
|
|
||||||
result2.get.Object mustEqual result.get.Object
|
|
||||||
}
|
|
||||||
|
|
||||||
"check a number (assigned and returned)" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val test = new TestClass()
|
|
||||||
val result : Option[LoanedKey] = obj.Available(5)
|
|
||||||
result.get.Policy mustEqual AvailabilityPolicy.Leased
|
|
||||||
result.get.Object = test
|
|
||||||
val result2 : Option[SecureKey] = obj.Get(5)
|
|
||||||
result2.get.Policy mustEqual AvailabilityPolicy.Leased
|
|
||||||
result2.get.Object.get === test
|
|
||||||
obj.Return(5) mustEqual Some(test)
|
|
||||||
val result3 : Option[SecureKey] = obj.Get(5)
|
|
||||||
result3.get.Policy mustEqual AvailabilityPolicy.Available
|
|
||||||
result3.get.Object mustEqual None
|
|
||||||
}
|
|
||||||
|
|
||||||
"clear" in {
|
|
||||||
val obj = MaxNumberSource()
|
|
||||||
val test1 = new TestClass()
|
|
||||||
val test2 = new TestClass()
|
|
||||||
obj.Available(5) //no assignment
|
|
||||||
obj.Available(10).get.Object = test1
|
|
||||||
obj.Available(15).get.Object = test2
|
|
||||||
obj.Restrict(15)
|
|
||||||
obj.Restrict(20).get.Object = test1
|
|
||||||
obj.CountUsed mustEqual 4
|
|
||||||
|
|
||||||
val list : List[IdentifiableEntity] = obj.Clear()
|
|
||||||
obj.CountUsed mustEqual 0
|
|
||||||
list.size mustEqual 3
|
|
||||||
list.count(obj => { obj == test1 }) mustEqual 2
|
|
||||||
list.count(obj => { obj == test2 }) mustEqual 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"LimitedNumberSource" should {
|
"LimitedNumberSource" should {
|
||||||
import net.psforever.objects.guid.source.LimitedNumberSource
|
import net.psforever.objects.guid.source.LimitedNumberSource
|
||||||
"construct" in {
|
"construct" in {
|
||||||
|
|
|
||||||
288
common/src/test/scala/objects/UniqueNumberSystemTest.scala
Normal file
288
common/src/test/scala/objects/UniqueNumberSystemTest.scala
Normal file
|
|
@ -0,0 +1,288 @@
|
||||||
|
// Copyright (c) 2017 PSForever
|
||||||
|
package objects
|
||||||
|
|
||||||
|
import akka.actor.{ActorRef, ActorSystem, Props}
|
||||||
|
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.LimitedNumberSource
|
||||||
|
|
||||||
|
import scala.concurrent.duration.Duration
|
||||||
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
|
class AllocateNumberPoolActors extends ActorTest(ActorSystem("test")) {
|
||||||
|
"AllocateNumberPoolActors" in {
|
||||||
|
val src : LimitedNumberSource = LimitedNumberSource(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(ActorSystem("test")) {
|
||||||
|
"UniqueNumberSystem" should {
|
||||||
|
"constructor" in {
|
||||||
|
val src : LimitedNumberSource = LimitedNumberSource(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(ActorSystem("test")) {
|
||||||
|
class EntityTestClass extends IdentifiableEntity
|
||||||
|
|
||||||
|
"UniqueNumberSystem" should {
|
||||||
|
"Register (success)" in {
|
||||||
|
val src : LimitedNumberSource = LimitedNumberSource(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(100, "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(100, "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(100, "ms"))
|
||||||
|
assert(msg.isInstanceOf[Success[_]])
|
||||||
|
assert(pool3.contains(testObj.GUID.guid))
|
||||||
|
}
|
||||||
|
assert(src.CountUsed == 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UniqueNumberSystemTest2 extends ActorTest(ActorSystem("test")) {
|
||||||
|
class EntityTestClass extends IdentifiableEntity
|
||||||
|
|
||||||
|
"UniqueNumberSystem" should {
|
||||||
|
"Register (success; already registered)" in {
|
||||||
|
val src : LimitedNumberSource = LimitedNumberSource(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(100, "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(100, "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(ActorSystem("test")) {
|
||||||
|
class EntityTestClass extends IdentifiableEntity
|
||||||
|
|
||||||
|
"UniqueNumberSystem" should {
|
||||||
|
"Register (failure; no pool)" in {
|
||||||
|
val src : LimitedNumberSource = LimitedNumberSource(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(100, "ms"))
|
||||||
|
assert(msg1.isInstanceOf[Failure[_]])
|
||||||
|
assert(!testObj.HasGUID)
|
||||||
|
assert(src.CountUsed == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UniqueNumberSystemTest4 extends ActorTest(ActorSystem("test")) {
|
||||||
|
class EntityTestClass extends IdentifiableEntity
|
||||||
|
|
||||||
|
"UniqueNumberSystem" should {
|
||||||
|
"Register (failure; empty pool)" in {
|
||||||
|
val src : LimitedNumberSource = LimitedNumberSource(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(100, "ms"))
|
||||||
|
assert(msg1.isInstanceOf[Success[_]]) //pool4 is now empty
|
||||||
|
|
||||||
|
val testObj2 = new EntityTestClass()
|
||||||
|
uns ! Register(testObj2, "pool4")
|
||||||
|
val msg2 = receiveOne(Duration.create(100, "ms"))
|
||||||
|
assert(msg2.isInstanceOf[Failure[_]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UniqueNumberSystemTest5 extends ActorTest(ActorSystem("test")) {
|
||||||
|
class EntityTestClass extends IdentifiableEntity
|
||||||
|
|
||||||
|
"UniqueNumberSystem" should {
|
||||||
|
"Unregister (success)" in {
|
||||||
|
val src : LimitedNumberSource = LimitedNumberSource(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(100, "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(100, "ms"))
|
||||||
|
assert(msg2.isInstanceOf[Success[_]])
|
||||||
|
assert(!testObj.HasGUID)
|
||||||
|
assert(src.CountUsed == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UniqueNumberSystemTest6 extends ActorTest(ActorSystem("test")) {
|
||||||
|
class EntityTestClass extends IdentifiableEntity
|
||||||
|
|
||||||
|
"UniqueNumberSystem" should {
|
||||||
|
"Unregister (success; object not registered at all)" in {
|
||||||
|
val src : LimitedNumberSource = LimitedNumberSource(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(100, "ms"))
|
||||||
|
assert(msg1.isInstanceOf[Success[_]])
|
||||||
|
assert(!testObj.HasGUID)
|
||||||
|
assert(src.CountUsed == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UniqueNumberSystemTest7 extends ActorTest(ActorSystem("test")) {
|
||||||
|
class EntityTestClass extends IdentifiableEntity
|
||||||
|
|
||||||
|
"UniqueNumberSystem" should {
|
||||||
|
"Unregister (failure; number not in system)" in {
|
||||||
|
val src : LimitedNumberSource = LimitedNumberSource(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 = net.psforever.packet.game.PlanetSideGUID(6001) //fake registering; number too high
|
||||||
|
assert(testObj.HasGUID)
|
||||||
|
assert(src.CountUsed == 0)
|
||||||
|
|
||||||
|
uns ! Unregister(testObj)
|
||||||
|
val msg1 = receiveOne(Duration.create(100, "ms"))
|
||||||
|
assert(msg1.isInstanceOf[Failure[_]])
|
||||||
|
assert(testObj.HasGUID)
|
||||||
|
assert(src.CountUsed == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UniqueNumberSystemTest8 extends ActorTest(ActorSystem("test")) {
|
||||||
|
class EntityTestClass extends IdentifiableEntity
|
||||||
|
|
||||||
|
"UniqueNumberSystem" should {
|
||||||
|
"Unregister (failure; object is not registered to that number)" in {
|
||||||
|
val src : LimitedNumberSource = LimitedNumberSource(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 = net.psforever.packet.game.PlanetSideGUID(3500) //fake registering
|
||||||
|
assert(testObj.HasGUID)
|
||||||
|
assert(src.CountUsed == 0)
|
||||||
|
|
||||||
|
uns ! Unregister(testObj)
|
||||||
|
val msg1 = receiveOne(Duration.create(100, "ms"))
|
||||||
|
assert(msg1.isInstanceOf[Failure[_]])
|
||||||
|
assert(testObj.HasGUID)
|
||||||
|
assert(src.CountUsed == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1175,7 +1175,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
def Execute(resolver : ActorRef) : Unit = {
|
def Execute(resolver : ActorRef) : Unit = {
|
||||||
localAccessor ! Register(localObject, resolver)
|
localAccessor ! Register(localObject, "dynamic", resolver)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue