refactor: factor out entity transfer state machine into a separate class to make code more analyzable
parent
8f87f55d05
commit
79f3ce2e9f
|
@ -46,35 +46,12 @@ using Nini.Config;
|
|||
|
||||
namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
||||
{
|
||||
/// <summary>
|
||||
/// The possible states that an agent can be in when its being transferred between regions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a state machine.
|
||||
///
|
||||
/// [Entry] => Preparing
|
||||
/// Preparing => { Transferring || CleaningUp || [Exit] }
|
||||
/// Transferring => { ReceivedAtDestination || CleaningUp }
|
||||
/// ReceivedAtDestination => CleaningUp
|
||||
/// CleaningUp => [Exit]
|
||||
///
|
||||
/// In other words, agents normally travel throwing Preparing => Transferring => ReceivedAtDestination => CleaningUp
|
||||
/// However, any state can transition to CleaningUp if the teleport has failed.
|
||||
/// </remarks>
|
||||
enum AgentTransferState
|
||||
{
|
||||
Preparing, // The agent is being prepared for transfer
|
||||
Transferring, // The agent is in the process of being transferred to a destination
|
||||
ReceivedAtDestination, // The destination has notified us that the agent has been successfully received
|
||||
CleaningUp // The agent is being changed to child/removed after a transfer
|
||||
}
|
||||
|
||||
public class EntityTransferModule : INonSharedRegionModule, IEntityTransferModule
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public const int DefaultMaxTransferDistance = 4095;
|
||||
public const bool EnableWaitForCallbackFromTeleportDestDefault = true;
|
||||
public const bool WaitForAgentArrivedAtDestinationDefault = true;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum distance, in standard region units (256m) that an agent is allowed to transfer.
|
||||
|
@ -85,13 +62,17 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
/// If true then on a teleport, the source region waits for a callback from the destination region. If
|
||||
/// a callback fails to arrive within a set time then the user is pulled back into the source region.
|
||||
/// </summary>
|
||||
public bool EnableWaitForCallbackFromTeleportDest { get; set; }
|
||||
public bool WaitForAgentArrivedAtDestination { get; set; }
|
||||
|
||||
protected bool m_Enabled = false;
|
||||
|
||||
protected Scene m_scene;
|
||||
public Scene Scene { get; private set; }
|
||||
|
||||
private Dictionary<UUID, AgentTransferState> m_agentsInTransit;
|
||||
/// <summary>
|
||||
/// Handles recording and manipulation of state for entities that are in transfer within or between regions
|
||||
/// (cross or teleport).
|
||||
/// </summary>
|
||||
private EntityTransferStateMachine m_entityTransferStateMachine;
|
||||
|
||||
private ExpiringCache<UUID, ExpiringCache<ulong, DateTime>> m_bannedRegions =
|
||||
new ExpiringCache<UUID, ExpiringCache<ulong, DateTime>>();
|
||||
|
@ -133,8 +114,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
IConfig transferConfig = source.Configs["EntityTransfer"];
|
||||
if (transferConfig != null)
|
||||
{
|
||||
EnableWaitForCallbackFromTeleportDest
|
||||
= transferConfig.GetBoolean("wait_for_callback", EnableWaitForCallbackFromTeleportDestDefault);
|
||||
WaitForAgentArrivedAtDestination
|
||||
= transferConfig.GetBoolean("wait_for_callback", WaitForAgentArrivedAtDestinationDefault);
|
||||
|
||||
MaxTransferDistance = transferConfig.GetInt("max_distance", DefaultMaxTransferDistance);
|
||||
}
|
||||
|
@ -143,7 +124,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
MaxTransferDistance = DefaultMaxTransferDistance;
|
||||
}
|
||||
|
||||
m_agentsInTransit = new Dictionary<UUID, AgentTransferState>();
|
||||
m_entityTransferStateMachine = new EntityTransferStateMachine(this);
|
||||
|
||||
m_Enabled = true;
|
||||
}
|
||||
|
||||
|
@ -156,7 +138,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
m_scene = scene;
|
||||
Scene = scene;
|
||||
|
||||
scene.RegisterModuleInterface<IEntityTransferModule>(this);
|
||||
scene.EventManager.OnNewClient += OnNewClient;
|
||||
|
@ -177,7 +159,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
m_eqModule = m_scene.RequestModuleInterface<IEventQueue>();
|
||||
m_eqModule = Scene.RequestModuleInterface<IEventQueue>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -226,7 +208,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
e.Message, e.StackTrace);
|
||||
|
||||
// Make sure that we clear the in-transit flag so that future teleport attempts don't always fail.
|
||||
ResetFromTransit(sp.UUID);
|
||||
m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
|
||||
|
||||
sp.ControllingClient.SendTeleportFailed("Internal error");
|
||||
}
|
||||
|
@ -245,7 +227,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
"[ENTITY TRANSFER MODULE]: Teleport for {0} to {1} within {2}",
|
||||
sp.Name, position, sp.Scene.RegionInfo.RegionName);
|
||||
|
||||
if (!SetInTransit(sp.UUID))
|
||||
if (!m_entityTransferStateMachine.SetInTransit(sp.UUID))
|
||||
{
|
||||
m_log.DebugFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Ignoring within region teleport request of {0} {1} to {2} - agent is already in transit.",
|
||||
|
@ -282,7 +264,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
position.Z = newPosZ;
|
||||
}
|
||||
|
||||
UpdateInTransit(sp.UUID, AgentTransferState.Transferring);
|
||||
m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring);
|
||||
|
||||
sp.ControllingClient.SendTeleportStart(teleportFlags);
|
||||
|
||||
|
@ -290,15 +272,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
sp.Velocity = Vector3.Zero;
|
||||
sp.Teleport(position);
|
||||
|
||||
UpdateInTransit(sp.UUID, AgentTransferState.ReceivedAtDestination);
|
||||
m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.ReceivedAtDestination);
|
||||
|
||||
foreach (SceneObjectGroup grp in sp.GetAttachments())
|
||||
{
|
||||
sp.Scene.EventManager.TriggerOnScriptChangedEvent(grp.LocalId, (uint)Changed.TELEPORT);
|
||||
}
|
||||
|
||||
UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp);
|
||||
ResetFromTransit(sp.UUID);
|
||||
m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp);
|
||||
m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -316,7 +298,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
{
|
||||
uint x = 0, y = 0;
|
||||
Utils.LongToUInts(regionHandle, out x, out y);
|
||||
GridRegion reg = m_scene.GridService.GetRegionByPosition(sp.Scene.RegionInfo.ScopeID, (int)x, (int)y);
|
||||
GridRegion reg = Scene.GridService.GetRegionByPosition(sp.Scene.RegionInfo.ScopeID, (int)x, (int)y);
|
||||
|
||||
if (reg != null)
|
||||
{
|
||||
|
@ -398,7 +380,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
{
|
||||
// Record that this agent is in transit so that we can prevent simultaneous requests and do later detection
|
||||
// of whether the destination region completes the teleport.
|
||||
if (!SetInTransit(sp.UUID))
|
||||
if (!m_entityTransferStateMachine.SetInTransit(sp.UUID))
|
||||
{
|
||||
m_log.DebugFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Ignoring teleport request of {0} {1} to {2} ({3}) {4}/{5} - agent is already in transit.",
|
||||
|
@ -410,7 +392,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
if (reg == null || finalDestination == null)
|
||||
{
|
||||
sp.ControllingClient.SendTeleportFailed("Unable to locate destination");
|
||||
ResetFromTransit(sp.UUID);
|
||||
m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -431,7 +413,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
sourceRegion.RegionName, sourceRegion.RegionLocX, sourceRegion.RegionLocY,
|
||||
MaxTransferDistance));
|
||||
|
||||
ResetFromTransit(sp.UUID);
|
||||
m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -451,7 +433,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
if (endPoint.Address == null)
|
||||
{
|
||||
sp.ControllingClient.SendTeleportFailed("Remote Region appears to be down");
|
||||
ResetFromTransit(sp.UUID);
|
||||
m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -469,11 +451,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
|
||||
string reason;
|
||||
string version;
|
||||
if (!m_scene.SimulationService.QueryAccess(
|
||||
if (!Scene.SimulationService.QueryAccess(
|
||||
finalDestination, sp.ControllingClient.AgentId, Vector3.Zero, out version, out reason))
|
||||
{
|
||||
sp.ControllingClient.SendTeleportFailed(reason);
|
||||
ResetFromTransit(sp.UUID);
|
||||
m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
|
||||
|
||||
m_log.DebugFormat(
|
||||
"[ENTITY TRANSFER MODULE]: {0} was stopped from teleporting from {1} to {2} because {3}",
|
||||
|
@ -527,7 +509,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
if (!CreateAgent(sp, reg, finalDestination, agentCircuit, teleportFlags, out reason, out logout))
|
||||
{
|
||||
sp.ControllingClient.SendTeleportFailed(String.Format("Teleport refused: {0}", reason));
|
||||
ResetFromTransit(sp.UUID);
|
||||
m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
|
||||
|
||||
m_log.DebugFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Teleport of {0} from {1} to {2} was refused because {3}",
|
||||
|
@ -537,7 +519,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
}
|
||||
|
||||
// Past this point we have to attempt clean up if the teleport fails, so update transfer state.
|
||||
UpdateInTransit(sp.UUID, AgentTransferState.Transferring);
|
||||
m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring);
|
||||
|
||||
// OK, it got this agent. Let's close some child agents
|
||||
sp.CloseChildAgents(newRegionX, newRegionY);
|
||||
|
@ -619,7 +601,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
// TeleportFinish makes the client send CompleteMovementIntoRegion (at the destination), which
|
||||
// trigers a whole shebang of things there, including MakeRoot. So let's wait for confirmation
|
||||
// that the client contacted the destination before we close things here.
|
||||
if (EnableWaitForCallbackFromTeleportDest && !WaitForCallback(sp.UUID))
|
||||
if (!m_entityTransferStateMachine.WaitForAgentArrivedAtDestination(sp.UUID))
|
||||
{
|
||||
m_log.WarnFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Teleport of {0} to {1} from {2} failed due to no callback from destination region. Returning avatar to source region.",
|
||||
|
@ -629,7 +611,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
return;
|
||||
}
|
||||
|
||||
UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp);
|
||||
m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp);
|
||||
|
||||
// For backwards compatibility
|
||||
if (version == "Unknown" || version == string.Empty)
|
||||
|
@ -679,12 +661,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
sp.UUID);
|
||||
}
|
||||
|
||||
ResetFromTransit(sp.UUID);
|
||||
m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
|
||||
}
|
||||
|
||||
protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout)
|
||||
{
|
||||
UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp);
|
||||
m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp);
|
||||
|
||||
// Client never contacted destination. Let's restore everything back
|
||||
sp.ControllingClient.SendTeleportFailed("Problems connecting to destination.");
|
||||
|
@ -696,17 +678,17 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
EnableChildAgents(sp);
|
||||
|
||||
// Finally, kill the agent we just created at the destination.
|
||||
m_scene.SimulationService.CloseAgent(finalDestination, sp.UUID);
|
||||
Scene.SimulationService.CloseAgent(finalDestination, sp.UUID);
|
||||
|
||||
sp.Scene.EventManager.TriggerTeleportFail(sp.ControllingClient, logout);
|
||||
|
||||
ResetFromTransit(sp.UUID);
|
||||
m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
|
||||
}
|
||||
|
||||
protected virtual bool CreateAgent(ScenePresence sp, GridRegion reg, GridRegion finalDestination, AgentCircuitData agentCircuit, uint teleportFlags, out string reason, out bool logout)
|
||||
{
|
||||
logout = false;
|
||||
bool success = m_scene.SimulationService.CreateAgent(finalDestination, agentCircuit, teleportFlags, out reason);
|
||||
bool success = Scene.SimulationService.CreateAgent(finalDestination, agentCircuit, teleportFlags, out reason);
|
||||
|
||||
if (success)
|
||||
sp.Scene.EventManager.TriggerTeleportStart(sp.ControllingClient, reg, finalDestination, teleportFlags, logout);
|
||||
|
@ -716,7 +698,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
|
||||
protected virtual bool UpdateAgent(GridRegion reg, GridRegion finalDestination, AgentData agent)
|
||||
{
|
||||
return m_scene.SimulationService.UpdateAgent(finalDestination, agent);
|
||||
return Scene.SimulationService.UpdateAgent(finalDestination, agent);
|
||||
}
|
||||
|
||||
protected virtual void SetCallbackURL(AgentData agent, RegionInfo region)
|
||||
|
@ -779,7 +761,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
/// <param name="position"></param>
|
||||
public virtual void RequestTeleportLandmark(IClientAPI remoteClient, AssetLandmark lm)
|
||||
{
|
||||
GridRegion info = m_scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID);
|
||||
GridRegion info = Scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID);
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
|
@ -800,12 +782,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
m_log.DebugFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Request to teleport {0} {1} home", client.Name, client.AgentId);
|
||||
|
||||
//OpenSim.Services.Interfaces.PresenceInfo pinfo = m_scene.PresenceService.GetAgent(client.SessionId);
|
||||
GridUserInfo uinfo = m_scene.GridUserService.GetGridUserInfo(client.AgentId.ToString());
|
||||
//OpenSim.Services.Interfaces.PresenceInfo pinfo = Scene.PresenceService.GetAgent(client.SessionId);
|
||||
GridUserInfo uinfo = Scene.GridUserService.GetGridUserInfo(client.AgentId.ToString());
|
||||
|
||||
if (uinfo != null)
|
||||
{
|
||||
GridRegion regionInfo = m_scene.GridService.GetRegionByUUID(UUID.Zero, uinfo.HomeRegionID);
|
||||
GridRegion regionInfo = Scene.GridService.GetRegionByUUID(UUID.Zero, uinfo.HomeRegionID);
|
||||
if (regionInfo == null)
|
||||
{
|
||||
// can't find the Home region: Tell viewer and abort
|
||||
|
@ -1114,7 +1096,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
|
||||
try
|
||||
{
|
||||
SetInTransit(agent.UUID);
|
||||
m_entityTransferStateMachine.SetInTransit(agent.UUID);
|
||||
|
||||
ulong neighbourHandle = Utils.UIntsToLong((uint)(neighbourx * Constants.RegionSize), (uint)(neighboury * Constants.RegionSize));
|
||||
|
||||
|
@ -1144,16 +1126,16 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
cAgent.CallbackURI = String.Empty;
|
||||
|
||||
// Beyond this point, extra cleanup is needed beyond removing transit state
|
||||
UpdateInTransit(agent.UUID, AgentTransferState.Transferring);
|
||||
m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.Transferring);
|
||||
|
||||
if (!m_scene.SimulationService.UpdateAgent(neighbourRegion, cAgent))
|
||||
{
|
||||
// region doesn't take it
|
||||
UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp);
|
||||
m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp);
|
||||
|
||||
ReInstantiateScripts(agent);
|
||||
agent.AddToPhysicalScene(isFlying);
|
||||
ResetFromTransit(agent.UUID);
|
||||
m_entityTransferStateMachine.ResetFromTransit(agent.UUID);
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
@ -1191,16 +1173,16 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
}
|
||||
|
||||
// SUCCESS!
|
||||
UpdateInTransit(agent.UUID, AgentTransferState.ReceivedAtDestination);
|
||||
m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.ReceivedAtDestination);
|
||||
|
||||
// Unlike a teleport, here we do not wait for the destination region to confirm the receipt.
|
||||
UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp);
|
||||
m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp);
|
||||
|
||||
agent.MakeChildAgent();
|
||||
|
||||
// FIXME: Possibly this should occur lower down after other commands to close other agents,
|
||||
// but not sure yet what the side effects would be.
|
||||
ResetFromTransit(agent.UUID);
|
||||
m_entityTransferStateMachine.ResetFromTransit(agent.UUID);
|
||||
|
||||
// now we have a child agent in this region. Request all interesting data about other (root) agents
|
||||
agent.SendOtherAgentsAvatarDataToMe();
|
||||
|
@ -1675,41 +1657,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Agent Arrived
|
||||
|
||||
public void AgentArrivedAtDestination(UUID id)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
{
|
||||
if (!m_agentsInTransit.ContainsKey(id))
|
||||
{
|
||||
m_log.WarnFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Region {0} received notification of arrival in destination scene of agent {1} but no teleport request is active",
|
||||
m_scene.RegionInfo.RegionName, id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AgentTransferState currentState = m_agentsInTransit[id];
|
||||
|
||||
if (currentState == AgentTransferState.ReceivedAtDestination)
|
||||
{
|
||||
// An anomoly but don't make this an outright failure - destination region could be overzealous in sending notification.
|
||||
m_log.WarnFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Region {0} received notification of arrival in destination scene of agent {1} but notification has already previously been received",
|
||||
m_scene.RegionInfo.RegionName, id);
|
||||
}
|
||||
else if (currentState != AgentTransferState.Transferring)
|
||||
{
|
||||
m_log.ErrorFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Region {0} received notification of arrival in destination scene of agent {1} but agent is in transfer state {2}",
|
||||
m_scene.RegionInfo.RegionName, id, currentState);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_agentsInTransit[id] = AgentTransferState.ReceivedAtDestination;
|
||||
}
|
||||
m_entityTransferStateMachine.SetAgentArrivedAtDestination(id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -1979,8 +1931,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
//// And the new channel...
|
||||
//if (m_interregionCommsOut != null)
|
||||
// successYN = m_interregionCommsOut.SendCreateObject(newRegionHandle, grp, true);
|
||||
if (m_scene.SimulationService != null)
|
||||
successYN = m_scene.SimulationService.CreateObject(destination, newPosition, grp, true);
|
||||
if (Scene.SimulationService != null)
|
||||
successYN = Scene.SimulationService.CreateObject(destination, newPosition, grp, true);
|
||||
|
||||
if (successYN)
|
||||
{
|
||||
|
@ -2051,139 +2003,9 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
|
||||
#region Misc
|
||||
|
||||
private bool WaitForCallback(UUID id)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
{
|
||||
if (!IsInTransit(id))
|
||||
throw new Exception(
|
||||
string.Format(
|
||||
"Asked to wait for destination callback for agent with ID {0} but it is not in transit"));
|
||||
|
||||
AgentTransferState currentState = m_agentsInTransit[id];
|
||||
|
||||
if (currentState != AgentTransferState.Transferring && currentState != AgentTransferState.ReceivedAtDestination)
|
||||
throw new Exception(
|
||||
string.Format(
|
||||
"Asked to wait for destination callback for agent with ID {0} but it is in state {1}",
|
||||
currentState));
|
||||
}
|
||||
|
||||
int count = 200;
|
||||
|
||||
// There should be no race condition here since no other code should be removing the agent transfer or
|
||||
// changing the state to another other than Transferring => ReceivedAtDestination.
|
||||
while (m_agentsInTransit[id] != AgentTransferState.ReceivedAtDestination && count-- > 0)
|
||||
{
|
||||
// m_log.Debug(" >>> Waiting... " + count);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set that an agent is in transit.
|
||||
/// </summary>
|
||||
/// <param name='id'>The ID of the agent being teleported</param>
|
||||
/// <returns>true if the agent was not already in transit, false if it was</returns>
|
||||
private bool SetInTransit(UUID id)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
{
|
||||
if (!m_agentsInTransit.ContainsKey(id))
|
||||
{
|
||||
m_agentsInTransit[id] = AgentTransferState.Preparing;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of an agent that is already in transit.
|
||||
/// </summary>
|
||||
/// <param name='id'></param>
|
||||
/// <param name='newState'></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref='Exception'>Illegal transitions will throw an Exception</exception>
|
||||
private void UpdateInTransit(UUID id, AgentTransferState newState)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
{
|
||||
// Illegal to try and update an agent that's not actually in transit.
|
||||
if (!m_agentsInTransit.ContainsKey(id))
|
||||
throw new Exception(string.Format("Agent with ID {0} is not registered as in transit", id));
|
||||
|
||||
AgentTransferState oldState = m_agentsInTransit[id];
|
||||
|
||||
bool transitionOkay = false;
|
||||
|
||||
if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp)
|
||||
transitionOkay = true;
|
||||
else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing)
|
||||
transitionOkay = true;
|
||||
else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring)
|
||||
transitionOkay = true;
|
||||
|
||||
if (transitionOkay)
|
||||
m_agentsInTransit[id] = newState;
|
||||
else
|
||||
throw new Exception(
|
||||
string.Format(
|
||||
"Agent with ID {0} is not allowed to move from old transit state {1} to new state {2}",
|
||||
id, oldState, newState));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInTransit(UUID id)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
return m_agentsInTransit.ContainsKey(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an agent from the transit state machine.
|
||||
/// </summary>
|
||||
/// <param name='id'></param>
|
||||
/// <returns>true if the agent was flagged as being teleported when this method was called, false otherwise</returns>
|
||||
private bool ResetFromTransit(UUID id)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
{
|
||||
if (m_agentsInTransit.ContainsKey(id))
|
||||
{
|
||||
AgentTransferState state = m_agentsInTransit[id];
|
||||
|
||||
if (state == AgentTransferState.Transferring || state == AgentTransferState.ReceivedAtDestination)
|
||||
{
|
||||
// FIXME: For now, we allow exit from any state since a thrown exception in teleport is now guranteed
|
||||
// to be handled properly - ResetFromTransit() could be invoked at any step along the process
|
||||
m_log.WarnFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Agent with ID {0} should not exit directly from state {1}, should go to {2} state first",
|
||||
id, state, AgentTransferState.CleaningUp);
|
||||
|
||||
// throw new Exception(
|
||||
// "Agent with ID {0} cannot exit directly from state {1}, it must go to {2} state first",
|
||||
// state, AgentTransferState.CleaningUp);
|
||||
}
|
||||
|
||||
m_agentsInTransit.Remove(id);
|
||||
|
||||
m_log.DebugFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Agent {0} cleared from transit in {1}",
|
||||
id, m_scene.RegionInfo.RegionName);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_log.WarnFormat(
|
||||
"[ENTITY TRANSFER MODULE]: Agent {0} requested to clear from transit in {1} but was already cleared.",
|
||||
id, m_scene.RegionInfo.RegionName);
|
||||
|
||||
return false;
|
||||
return m_entityTransferStateMachine.IsInTransit(id);
|
||||
}
|
||||
|
||||
protected void ReInstantiateScripts(ScenePresence sp)
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using OpenMetaverse;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Capabilities;
|
||||
using OpenSim.Framework.Client;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
using OpenSim.Region.Physics.Manager;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using GridRegion = OpenSim.Services.Interfaces.GridRegion;
|
||||
|
||||
namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
||||
{
|
||||
/// <summary>
|
||||
/// The possible states that an agent can be in when its being transferred between regions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a state machine.
|
||||
///
|
||||
/// [Entry] => Preparing
|
||||
/// Preparing => { Transferring || CleaningUp || [Exit] }
|
||||
/// Transferring => { ReceivedAtDestination || CleaningUp }
|
||||
/// ReceivedAtDestination => CleaningUp
|
||||
/// CleaningUp => [Exit]
|
||||
///
|
||||
/// In other words, agents normally travel throwing Preparing => Transferring => ReceivedAtDestination => CleaningUp
|
||||
/// However, any state can transition to CleaningUp if the teleport has failed.
|
||||
/// </remarks>
|
||||
enum AgentTransferState
|
||||
{
|
||||
Preparing, // The agent is being prepared for transfer
|
||||
Transferring, // The agent is in the process of being transferred to a destination
|
||||
ReceivedAtDestination, // The destination has notified us that the agent has been successfully received
|
||||
CleaningUp // The agent is being changed to child/removed after a transfer
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records the state of entities when they are in transfer within or between regions (cross or teleport).
|
||||
/// </summary>
|
||||
public class EntityTransferStateMachine
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
/// <summary>
|
||||
/// If true then on a teleport, the source region waits for a callback from the destination region. If
|
||||
/// a callback fails to arrive within a set time then the user is pulled back into the source region.
|
||||
/// </summary>
|
||||
public bool EnableWaitForAgentArrivedAtDestination { get; set; }
|
||||
|
||||
private EntityTransferModule m_mod;
|
||||
|
||||
private Dictionary<UUID, AgentTransferState> m_agentsInTransit = new Dictionary<UUID, AgentTransferState>();
|
||||
|
||||
public EntityTransferStateMachine(EntityTransferModule module)
|
||||
{
|
||||
m_mod = module;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set that an agent is in transit.
|
||||
/// </summary>
|
||||
/// <param name='id'>The ID of the agent being teleported</param>
|
||||
/// <returns>true if the agent was not already in transit, false if it was</returns>
|
||||
internal bool SetInTransit(UUID id)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
{
|
||||
if (!m_agentsInTransit.ContainsKey(id))
|
||||
{
|
||||
m_agentsInTransit[id] = AgentTransferState.Preparing;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of an agent that is already in transit.
|
||||
/// </summary>
|
||||
/// <param name='id'></param>
|
||||
/// <param name='newState'></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref='Exception'>Illegal transitions will throw an Exception</exception>
|
||||
internal void UpdateInTransit(UUID id, AgentTransferState newState)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
{
|
||||
// Illegal to try and update an agent that's not actually in transit.
|
||||
if (!m_agentsInTransit.ContainsKey(id))
|
||||
throw new Exception(
|
||||
string.Format(
|
||||
"Agent with ID {0} is not registered as in transit in {1}",
|
||||
id, m_mod.Scene.RegionInfo.RegionName));
|
||||
|
||||
AgentTransferState oldState = m_agentsInTransit[id];
|
||||
|
||||
bool transitionOkay = false;
|
||||
|
||||
if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp)
|
||||
transitionOkay = true;
|
||||
else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing)
|
||||
transitionOkay = true;
|
||||
else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring)
|
||||
transitionOkay = true;
|
||||
|
||||
if (transitionOkay)
|
||||
m_agentsInTransit[id] = newState;
|
||||
else
|
||||
throw new Exception(
|
||||
string.Format(
|
||||
"Agent with ID {0} is not allowed to move from old transit state {1} to new state {2} in {3}",
|
||||
id, oldState, newState, m_mod.Scene.RegionInfo.RegionName));
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsInTransit(UUID id)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
return m_agentsInTransit.ContainsKey(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an agent from the transit state machine.
|
||||
/// </summary>
|
||||
/// <param name='id'></param>
|
||||
/// <returns>true if the agent was flagged as being teleported when this method was called, false otherwise</returns>
|
||||
internal bool ResetFromTransit(UUID id)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
{
|
||||
if (m_agentsInTransit.ContainsKey(id))
|
||||
{
|
||||
AgentTransferState state = m_agentsInTransit[id];
|
||||
|
||||
if (state == AgentTransferState.Transferring || state == AgentTransferState.ReceivedAtDestination)
|
||||
{
|
||||
// FIXME: For now, we allow exit from any state since a thrown exception in teleport is now guranteed
|
||||
// to be handled properly - ResetFromTransit() could be invoked at any step along the process
|
||||
m_log.WarnFormat(
|
||||
"[ENTITY TRANSFER STATE MACHINE]: Agent with ID {0} should not exit directly from state {1}, should go to {2} state first in {3}",
|
||||
id, state, AgentTransferState.CleaningUp, m_mod.Scene.RegionInfo.RegionName);
|
||||
|
||||
// throw new Exception(
|
||||
// "Agent with ID {0} cannot exit directly from state {1}, it must go to {2} state first",
|
||||
// state, AgentTransferState.CleaningUp);
|
||||
}
|
||||
|
||||
m_agentsInTransit.Remove(id);
|
||||
|
||||
m_log.DebugFormat(
|
||||
"[ENTITY TRANSFER STATE MACHINE]: Agent {0} cleared from transit in {1}",
|
||||
id, m_mod.Scene.RegionInfo.RegionName);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_log.WarnFormat(
|
||||
"[ENTITY TRANSFER STATE MACHINE]: Agent {0} requested to clear from transit in {1} but was already cleared",
|
||||
id, m_mod.Scene.RegionInfo.RegionName);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool WaitForAgentArrivedAtDestination(UUID id)
|
||||
{
|
||||
if (!m_mod.WaitForAgentArrivedAtDestination)
|
||||
return true;
|
||||
|
||||
lock (m_agentsInTransit)
|
||||
{
|
||||
if (!IsInTransit(id))
|
||||
throw new Exception(
|
||||
string.Format(
|
||||
"Asked to wait for destination callback for agent with ID {0} in {1} but agent is not in transit",
|
||||
id, m_mod.Scene.RegionInfo.RegionName));
|
||||
|
||||
AgentTransferState currentState = m_agentsInTransit[id];
|
||||
|
||||
if (currentState != AgentTransferState.Transferring && currentState != AgentTransferState.ReceivedAtDestination)
|
||||
throw new Exception(
|
||||
string.Format(
|
||||
"Asked to wait for destination callback for agent with ID {0} in {1} but agent is in state {2}",
|
||||
id, m_mod.Scene.RegionInfo.RegionName, currentState));
|
||||
}
|
||||
|
||||
int count = 200;
|
||||
|
||||
// There should be no race condition here since no other code should be removing the agent transfer or
|
||||
// changing the state to another other than Transferring => ReceivedAtDestination.
|
||||
while (m_agentsInTransit[id] != AgentTransferState.ReceivedAtDestination && count-- > 0)
|
||||
{
|
||||
// m_log.Debug(" >>> Waiting... " + count);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
internal void SetAgentArrivedAtDestination(UUID id)
|
||||
{
|
||||
lock (m_agentsInTransit)
|
||||
{
|
||||
if (!m_agentsInTransit.ContainsKey(id))
|
||||
{
|
||||
m_log.WarnFormat(
|
||||
"[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but no teleport request is active",
|
||||
m_mod.Scene.RegionInfo.RegionName, id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AgentTransferState currentState = m_agentsInTransit[id];
|
||||
|
||||
if (currentState == AgentTransferState.ReceivedAtDestination)
|
||||
{
|
||||
// An anomoly but don't make this an outright failure - destination region could be overzealous in sending notification.
|
||||
m_log.WarnFormat(
|
||||
"[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but notification has already previously been received",
|
||||
m_mod.Scene.RegionInfo.RegionName, id);
|
||||
}
|
||||
else if (currentState != AgentTransferState.Transferring)
|
||||
{
|
||||
m_log.ErrorFormat(
|
||||
"[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but agent is in state {2}",
|
||||
m_mod.Scene.RegionInfo.RegionName, id, currentState);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_agentsInTransit[id] = AgentTransferState.ReceivedAtDestination;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -117,7 +117,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
|
||||
protected override GridRegion GetFinalDestination(GridRegion region)
|
||||
{
|
||||
int flags = m_scene.GridService.GetRegionFlags(m_scene.RegionInfo.ScopeID, region.RegionID);
|
||||
int flags = Scene.GridService.GetRegionFlags(Scene.RegionInfo.ScopeID, region.RegionID);
|
||||
m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: region {0} flags: {1}", region.RegionID, flags);
|
||||
|
||||
if ((flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0)
|
||||
|
@ -139,7 +139,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
if (base.NeedsClosing(drawdist, oldRegionX, newRegionX, oldRegionY, newRegionY, reg))
|
||||
return true;
|
||||
|
||||
int flags = m_scene.GridService.GetRegionFlags(m_scene.RegionInfo.ScopeID, reg.RegionID);
|
||||
int flags = Scene.GridService.GetRegionFlags(Scene.RegionInfo.ScopeID, reg.RegionID);
|
||||
if (flags == -1 /* no region in DB */ || (flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0)
|
||||
return true;
|
||||
|
||||
|
@ -152,7 +152,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
if (logout)
|
||||
{
|
||||
// Log them out of this grid
|
||||
m_scene.PresenceService.LogoutAgent(sp.ControllingClient.SessionId);
|
||||
Scene.PresenceService.LogoutAgent(sp.ControllingClient.SessionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: CreateAgent {0} {1}", reg.ServerURI, finalDestination.ServerURI);
|
||||
reason = string.Empty;
|
||||
logout = false;
|
||||
int flags = m_scene.GridService.GetRegionFlags(m_scene.RegionInfo.ScopeID, reg.RegionID);
|
||||
int flags = Scene.GridService.GetRegionFlags(Scene.RegionInfo.ScopeID, reg.RegionID);
|
||||
if (flags == -1 /* no region in DB */ || (flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0)
|
||||
{
|
||||
// this user is going to another grid
|
||||
|
@ -201,7 +201,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
"[ENTITY TRANSFER MODULE]: Request to teleport {0} {1} home", client.Name, client.AgentId);
|
||||
|
||||
// Let's find out if this is a foreign user or a local user
|
||||
IUserManagement uMan = m_scene.RequestModuleInterface<IUserManagement>();
|
||||
IUserManagement uMan = Scene.RequestModuleInterface<IUserManagement>();
|
||||
if (uMan != null && uMan.IsLocalGridUser(id))
|
||||
{
|
||||
// local grid user
|
||||
|
@ -265,7 +265,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
return;
|
||||
}
|
||||
|
||||
GridRegion info = m_scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID);
|
||||
GridRegion info = Scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID);
|
||||
|
||||
// Local region?
|
||||
if (info != null)
|
||||
|
@ -335,8 +335,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
|||
}
|
||||
|
||||
// Let's find out if this is a foreign user or a local user
|
||||
IUserManagement uMan = m_scene.RequestModuleInterface<IUserManagement>();
|
||||
// UserAccount account = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, obj.AgentId);
|
||||
IUserManagement uMan = Scene.RequestModuleInterface<IUserManagement>();
|
||||
// UserAccount account = Scene.UserAccountService.GetUserAccount(Scene.RegionInfo.ScopeID, obj.AgentId);
|
||||
if (uMan != null && uMan.IsLocalGridUser(obj.AgentId))
|
||||
{
|
||||
// local grid user
|
||||
|
|
Loading…
Reference in New Issue