switched NumberPoolAccessorActor out for UniqueNumberSystem; created file that enumerates object types for future reference

cleaned up folder object/guid, except for contents of objects/guid/selectors
This commit is contained in:
FateJH 2017-09-27 20:18:37 -04:00
parent 0a0a416585
commit 92b6883fa0
14 changed files with 103 additions and 1291 deletions

View 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"
}

View file

@ -1,313 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid
import net.psforever.objects.entity.{IdentifiableEntity, NoGUIDException}
import net.psforever.objects.guid.key.LoanedKey
import net.psforever.objects.guid.pool.{ExclusivePool, GenericPool, NumberPool}
import net.psforever.objects.guid.source.NumberSource
import net.psforever.packet.game.PlanetSideGUID
import scala.util.{Failure, Success, Try}
class NumberPoolHub2(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
def apply(number : PlanetSideGUID) : Option[IdentifiableEntity] = this(number.guid)
def apply(number : Int) : Option[IdentifiableEntity] = source.Get(number).orElse(return None).get.Object
def Numbers : List[Int] = bigpool.keys.map(key => key.toInt).toList
def AddPool(name : String, pool : List[Int]) : NumberPool = {
if(hash.get(name).isDefined) {
throw new IllegalArgumentException(s"can not add pool $name - name already known to this hub?")
}
if(source.Size <= pool.max) {
throw new IllegalArgumentException(s"can not add pool $name - max(pool) is greater than source.size")
}
val collision = bigpool.keys.map(n => n.toInt).toSet.intersect(pool.toSet)
if(collision.nonEmpty) {
throw new IllegalArgumentException(s"can not add pool $name - it contains the following redundant numbers: ${collision.toString}")
}
pool.foreach(i => bigpool += i.toLong -> name)
hash += name -> new ExclusivePool(pool)
hash(name)
}
def RemovePool(name : String) : List[Int] = {
if(name.equals("generic") || name.equals("")) {
throw new IllegalArgumentException("can not remove pool - generic or restricted")
}
val pool = hash.get(name).orElse({
throw new IllegalArgumentException(s"can not remove pool - $name does not exist")
}).get
if(pool.Count > 0) {
throw new IllegalArgumentException(s"can not remove pool - $name is being used")
}
hash.remove(name)
pool.Numbers.foreach(number => bigpool -= number)
pool.Numbers
}
def GetPool(name : String) : Option[NumberPool] = if(name.equals("")) { None } else { hash.get(name) }
def Pools : mutable.HashMap[String, NumberPool] = hash
def WhichPool(number : Int) : Option[String] = {
val name = bigpool.get(number)
if(name.contains("")) { None } else { name }
}
def WhichPool(obj : IdentifiableEntity) : Option[String] = {
try {
val number : Int = obj.GUID.guid
val entry = source.Get(number)
if(entry.isDefined && entry.get.Object.contains(obj)) { WhichPool(number) } else { None }
}
catch {
case _ : Exception =>
None
}
}
def register(obj : IdentifiableEntity) : Try[Int] = register(obj, "generic")
def register(obj : IdentifiableEntity, number : Int) : Try[Int] = {
bigpool.get(number.toLong) match {
case Some(name) =>
register_GetSpecificNumberFromPool(name, number) match {
case Success(key) =>
key.Object = obj
Success(obj.GUID.guid)
case Failure(ex) =>
Failure(new Exception(s"trying to register an object to a specific number but, ${ex.getMessage}"))
}
case None =>
import net.psforever.objects.guid.selector.SpecificSelector
hash("generic").Selector.asInstanceOf[SpecificSelector].SelectionIndex = number
register(obj, "generic")
}
}
private def register_GetSpecificNumberFromPool(name : String, number : Int) : Try[LoanedKey]= {
hash.get(name) match {
case Some(pool) =>
val slctr = pool.Selector
import net.psforever.objects.guid.selector.SpecificSelector
val specific = new SpecificSelector
specific.SelectionIndex = number
pool.Selector = specific
pool.Get()
pool.Selector = slctr
register_GetAvailableNumberFromSource(number)
case None =>
Failure(new Exception(s"number pool $name not defined"))
}
}
private def register_GetAvailableNumberFromSource(number : Int) : Try[LoanedKey] = {
source.Available(number) match {
case Some(key) =>
Success(key)
case None =>
Failure(new Exception(s"number $number is unavailable"))
}
}
def register(obj : IdentifiableEntity, name : String) : Try[Int] = {
try {
register_CheckNumberAgainstDesiredPool(obj, name, obj.GUID.guid)
}
catch {
case _ : Exception =>
register_GetPool(name) match {
case Success(key) =>
key.Object = obj
Success(obj.GUID.guid)
case Failure(ex) =>
Failure(new Exception(s"trying to register an object but, ${ex.getMessage}"))
}
}
}
private def register_CheckNumberAgainstDesiredPool(obj : IdentifiableEntity, name : String, number : Int) : Try[Int] = {
val directKey = source.Get(number)
if(directKey.isEmpty || !directKey.get.Object.contains(obj)) {
Failure(new Exception("object already registered, but not to this source"))
}
else if(!WhichPool(number).contains(name)) {
//TODO obj is not registered to the desired pool; is this okay?
Success(number)
}
else {
Success(number)
}
}
private def register_GetPool(name : String) : Try[LoanedKey] = {
hash.get(name) match {
case Some(pool) =>
register_GetNumberFromDesiredPool(pool)
case _ =>
Failure(new Exception(s"number pool $name not defined"))
}
}
private def register_GetNumberFromDesiredPool(pool : NumberPool) : Try[LoanedKey] = {
pool.Get() match {
case Success(number) =>
register_GetMonitorFromSource(number)
case Failure(ex) =>
Failure(ex)
}
}
private def register_GetMonitorFromSource(number : Int) : Try[LoanedKey] = {
source.Available(number) match {
case Some(key) =>
Success(key)
case _ =>
throw NoGUIDException(s"a pool gave us a number $number that is actually unavailable") //stop the show; this is terrible!
}
}
def register(number : Int) : Try[LoanedKey] = {
WhichPool(number) match {
case None =>
import net.psforever.objects.guid.selector.SpecificSelector
hash("generic").Selector.asInstanceOf[SpecificSelector].SelectionIndex = number
register_GetPool("generic")
case Some(name) =>
register_GetSpecificNumberFromPool(name, number)
}
}
def register(name : String) : Try[LoanedKey] = register_GetPool(name)
def latterPartRegister(obj : IdentifiableEntity, number : Int) : Try[IdentifiableEntity] = {
register_GetMonitorFromSource(number) match {
case Success(monitor) =>
monitor.Object = obj
Success(obj)
case Failure(ex) =>
Failure(ex)
}
}
def unregister(obj : IdentifiableEntity) : Try[Int] = {
unregister_GetPoolFromObject(obj) match {
case Success(pool) =>
val number = obj.GUID.guid
pool.Return(number)
source.Return(number)
obj.Invalidate()
Success(number)
case Failure(ex) =>
Failure(new Exception(s"can not unregister this object: ${ex.getMessage}"))
}
}
def unregister_GetPoolFromObject(obj : IdentifiableEntity) : Try[NumberPool] = {
WhichPool(obj) match {
case Some(name) =>
unregister_GetPool(name)
case None =>
Failure(throw new Exception("can not find a pool for this object"))
}
}
private def unregister_GetPool(name : String) : Try[NumberPool] = {
hash.get(name) match {
case Some(pool) =>
Success(pool)
case None =>
Failure(new Exception(s"no pool by the name of '$name'"))
}
}
def unregister(number : Int) : Try[Option[IdentifiableEntity]] = {
if(source.Test(number)) {
unregister_GetObjectFromSource(number)
}
else {
Failure(new Exception(s"can not unregister a number $number that this source does not own") )
}
}
private def unregister_GetObjectFromSource(number : Int) : Try[Option[IdentifiableEntity]] = {
source.Return(number) match {
case Some(obj) =>
unregister_ReturnObjectToPool(obj)
case None =>
unregister_ReturnNumberToPool(number) //nothing is wrong, but we'll check the pool
}
}
private def unregister_ReturnObjectToPool(obj : IdentifiableEntity) : Try[Option[IdentifiableEntity]] = {
val number = obj.GUID.guid
unregister_GetPoolFromNumber(number) match {
case Success(pool) =>
pool.Return(number)
obj.Invalidate()
Success(Some(obj))
case Failure(ex) =>
source.Available(number) //undo
Failure(new Exception(s"started unregistering, but ${ex.getMessage}"))
}
}
private def unregister_ReturnNumberToPool(number : Int) : Try[Option[IdentifiableEntity]] = {
unregister_GetPoolFromNumber(number) match {
case Success(pool) =>
pool.Return(number)
Success(None)
case _ => //though everything else went fine, we must still fail if this number was restricted all along
if(!bigpool.get(number).contains("")) {
Success(None)
}
else {
Failure(new Exception(s"can not unregister this number $number"))
}
}
}
private def unregister_GetPoolFromNumber(number : Int) : Try[NumberPool] = {
WhichPool(number) match {
case Some(name) =>
unregister_GetPool(name)
case None =>
Failure(new Exception(s"no pool using number $number"))
}
}
def latterPartUnregister(number : Int) : Option[IdentifiableEntity] = source.Return(number)
def isRegistered(obj : IdentifiableEntity) : Boolean = {
try {
source.Get(obj.GUID.guid) match {
case Some(monitor) =>
monitor.Object.contains(obj)
case None =>
false
}
}
catch {
case _ : NoGUIDException =>
false
}
}
def isRegistered(number : Int) : Boolean = {
source.Get(number) match {
case Some(monitor) =>
monitor.Policy == AvailabilityPolicy.Leased
case None =>
false
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ import akka.actor.{ActorContext, ActorRef, Props}
import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.objects.equipment.Equipment
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.source.LimitedNumberSource
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
/** The basic support structure for the globally unique number system used by this `Zone`. */
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. */
private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]()
/** 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 = {
if(accessor == ActorRef.noSender) {
//TODO wrong initialization for GUID
implicit val guid = this.guid
//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")
implicit val guid : NumberPoolHub = this.guid //passed into builderObject.Build implicitly
accessor = context.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid)), s"$Id-uns")
ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground")
Map.LocalObjects.foreach({ builderObject =>

View file

@ -2,16 +2,11 @@
package objects
import akka.actor.{ActorSystem, Props}
import akka.testkit.TestProbe
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.actor.{NumberPoolAccessorActor, NumberPoolActor, Register}
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.source.LimitedNumberSource
import scala.concurrent.duration.Duration
import scala.util.Success
class NumberPoolActorTest extends ActorTest(ActorSystem("test")) {
"NumberPoolActor" should {
@ -53,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
}
}
}

View file

@ -9,180 +9,6 @@ class NumberSourceTest extends Specification {
import net.psforever.objects.entity.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 {
import net.psforever.objects.guid.source.LimitedNumberSource
"construct" in {

View file

@ -1175,7 +1175,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
def Execute(resolver : ActorRef) : Unit = {
localAccessor ! Register(localObject, resolver)
localAccessor ! Register(localObject, "dynamic", resolver)
}
})
}