* Re-enables map item requests.
* Puts remote requests in a single worker thread * Worker thread only starts when there are agents to serve * When there are no agents to serve, it shuts down * A good example of how to deal with threads in non-shared modules so they don't end up consuming threads per regions0.6.0-stable
parent
2947ef9c00
commit
2c5497fa3a
|
@ -33,6 +33,7 @@ using System.Drawing.Imaging;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
using OpenMetaverse.Imaging;
|
using OpenMetaverse.Imaging;
|
||||||
using OpenMetaverse.StructuredData;
|
using OpenMetaverse.StructuredData;
|
||||||
|
@ -60,6 +61,8 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
|
||||||
|
|
||||||
private static readonly string m_mapLayerPath = "0001/";
|
private static readonly string m_mapLayerPath = "0001/";
|
||||||
|
|
||||||
|
private OpenSim.Framework.BlockingQueue<MapRequestState> requests = new OpenSim.Framework.BlockingQueue<MapRequestState>();
|
||||||
|
|
||||||
//private IConfig m_config;
|
//private IConfig m_config;
|
||||||
private Scene m_scene;
|
private Scene m_scene;
|
||||||
private List<MapBlockData> cachedMapBlocks = new List<MapBlockData>();
|
private List<MapBlockData> cachedMapBlocks = new List<MapBlockData>();
|
||||||
|
@ -67,7 +70,11 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
|
||||||
private byte[] myMapImageJPEG;
|
private byte[] myMapImageJPEG;
|
||||||
private bool m_Enabled = false;
|
private bool m_Enabled = false;
|
||||||
private Dictionary<UUID, MapRequestState> m_openRequests = new Dictionary<UUID, MapRequestState>();
|
private Dictionary<UUID, MapRequestState> m_openRequests = new Dictionary<UUID, MapRequestState>();
|
||||||
|
private Dictionary<string, int> m_blacklistedurls = new Dictionary<string, int>();
|
||||||
|
private Dictionary<ulong, int> m_blacklistedregions = new Dictionary<ulong, int>();
|
||||||
|
private Dictionary<ulong, string> m_cachedRegionMapItemsAddress = new Dictionary<ulong, string>();
|
||||||
|
private Thread mapItemReqThread;
|
||||||
|
private volatile bool threadrunning = false;
|
||||||
|
|
||||||
//private int CacheRegionsDistance = 256;
|
//private int CacheRegionsDistance = 256;
|
||||||
|
|
||||||
|
@ -93,12 +100,15 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
|
||||||
|
|
||||||
m_scene.AddHTTPHandler(regionimage, OnHTTPGetMapImage);
|
m_scene.AddHTTPHandler(regionimage, OnHTTPGetMapImage);
|
||||||
m_scene.AddLLSDHandler("/MAP/MapItems/" + scene.RegionInfo.RegionHandle.ToString(),HandleRemoteMapItemRequest);
|
m_scene.AddLLSDHandler("/MAP/MapItems/" + scene.RegionInfo.RegionHandle.ToString(),HandleRemoteMapItemRequest);
|
||||||
//QuadTree.Subdivide();
|
|
||||||
//QuadTree.Subdivide();
|
|
||||||
|
|
||||||
scene.EventManager.OnRegisterCaps += OnRegisterCaps;
|
scene.EventManager.OnRegisterCaps += OnRegisterCaps;
|
||||||
scene.EventManager.OnNewClient += OnNewClient;
|
scene.EventManager.OnNewClient += OnNewClient;
|
||||||
scene.EventManager.OnClientClosed += ClientLoggedOut;
|
scene.EventManager.OnClientClosed += ClientLoggedOut;
|
||||||
|
scene.EventManager.OnMakeChildAgent += MakeChildAgent;
|
||||||
|
scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
public void PostInitialise()
|
public void PostInitialise()
|
||||||
{
|
{
|
||||||
|
@ -220,33 +230,80 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
|
||||||
}
|
}
|
||||||
#region EventHandlers
|
#region EventHandlers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registered for event
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
private void OnNewClient(IClientAPI client)
|
private void OnNewClient(IClientAPI client)
|
||||||
{
|
{
|
||||||
// All friends establishment protocol goes over instant message
|
|
||||||
// There's no way to send a message from the sim
|
|
||||||
// to a user to 'add a friend' without causing dialog box spam
|
|
||||||
//
|
|
||||||
// The base set of friends are added when the user signs on in their XMLRPC response
|
|
||||||
// Generated by LoginService. The friends are retreived from the database by the UserManager
|
|
||||||
|
|
||||||
// Subscribe to instant messages
|
|
||||||
|
|
||||||
//client.OnInstantMessage += OnInstantMessage;
|
|
||||||
//client.OnApproveFriendRequest += OnApprovedFriendRequest;
|
|
||||||
//client.OnDenyFriendRequest += OnDenyFriendRequest;
|
|
||||||
//client.OnTerminateFriendship += OnTerminateFriendship;
|
|
||||||
|
|
||||||
//doFriendListUpdateOnline(client.AgentId);
|
|
||||||
client.OnRequestMapBlocks += RequestMapBlocks;
|
client.OnRequestMapBlocks += RequestMapBlocks;
|
||||||
client.OnMapItemRequest += HandleMapItemRequest;
|
client.OnMapItemRequest += HandleMapItemRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client logged out, check to see if there are any more root agents in the simulator
|
||||||
|
/// If not, stop the mapItemRequest Thread
|
||||||
|
/// Event handler
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="AgentId">AgentID that logged out</param>
|
||||||
private void ClientLoggedOut(UUID AgentId)
|
private void ClientLoggedOut(UUID AgentId)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
List<ScenePresence> presences = m_scene.GetAvatars();
|
||||||
|
int rootcount = 0;
|
||||||
|
for (int i=0;i<presences.Count;i++)
|
||||||
|
{
|
||||||
|
if (presences[i] != null)
|
||||||
|
{
|
||||||
|
if (!presences[i].IsChildAgent)
|
||||||
|
rootcount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rootcount <= 1)
|
||||||
|
StopThread();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the MapItemRequest Thread
|
||||||
|
/// Note that this only gets started when there are actually agents in the region
|
||||||
|
/// Additionally, it gets stopped when there are none.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="o"></param>
|
||||||
|
private void StartThread(object o)
|
||||||
|
{
|
||||||
|
if (threadrunning) return;
|
||||||
|
|
||||||
|
m_log.Warn("[WorldMap]: Starting remote MapItem request thread");
|
||||||
|
threadrunning = true;
|
||||||
|
mapItemReqThread = new Thread(new ThreadStart(process));
|
||||||
|
mapItemReqThread.IsBackground = true;
|
||||||
|
mapItemReqThread.Name = "MapItemRequestThread";
|
||||||
|
mapItemReqThread.Priority = ThreadPriority.BelowNormal;
|
||||||
|
mapItemReqThread.SetApartmentState(ApartmentState.MTA);
|
||||||
|
mapItemReqThread.Start();
|
||||||
|
ThreadTracker.Add(mapItemReqThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueues a 'stop thread' MapRequestState. Causes the MapItemRequest thread to end
|
||||||
|
/// </summary>
|
||||||
|
private void StopThread()
|
||||||
|
{
|
||||||
|
MapRequestState st = new MapRequestState();
|
||||||
|
st.agentID=UUID.Zero;
|
||||||
|
st.EstateID=0;
|
||||||
|
st.flags=0;
|
||||||
|
st.godlike=false;
|
||||||
|
st.itemtype=0;
|
||||||
|
st.regionhandle=0;
|
||||||
|
|
||||||
|
requests.Enqueue(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public virtual void HandleMapItemRequest(IClientAPI remoteClient, uint flags,
|
public virtual void HandleMapItemRequest(IClientAPI remoteClient, uint flags,
|
||||||
uint EstateID, bool godlike, uint itemtype, ulong regionhandle)
|
uint EstateID, bool godlike, uint itemtype, ulong regionhandle)
|
||||||
{
|
{
|
||||||
|
@ -257,6 +314,7 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
|
||||||
{
|
{
|
||||||
if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle)
|
if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle)
|
||||||
{
|
{
|
||||||
|
// Local Map Item Request
|
||||||
List<ScenePresence> avatars = m_scene.GetAvatars();
|
List<ScenePresence> avatars = m_scene.GetAvatars();
|
||||||
int tc = System.Environment.TickCount;
|
int tc = System.Environment.TickCount;
|
||||||
List<mapItemReply> mapitems = new List<mapItemReply>();
|
List<mapItemReply> mapitems = new List<mapItemReply>();
|
||||||
|
@ -295,29 +353,62 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//RegionInfo mreg = m_scene.SceneGridService.RequestNeighbouringRegionInfo(regionhandle);
|
// Remote Map Item Request
|
||||||
//if (mreg != null)
|
|
||||||
//{
|
|
||||||
// string httpserver = "http://" + mreg.ExternalEndPoint.Address.ToString() + ":" + mreg.HttpPort + "/MAP/MapItems/" + regionhandle.ToString();
|
|
||||||
|
|
||||||
// RequestMapItems(httpserver,remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle);
|
|
||||||
|
|
||||||
//}
|
// ensures that the blockingqueue doesn't get borked if the GetAgents() timing changes.
|
||||||
}
|
// Note that we only start up a remote mapItem Request thread if there's users who could
|
||||||
}
|
// be making requests
|
||||||
|
if (!threadrunning)
|
||||||
}
|
|
||||||
|
|
||||||
public delegate LLSDMap RequestMapItemsDelegate(string httpserver, UUID id, uint flags,
|
|
||||||
uint EstateID, bool godlike, uint itemtype, ulong regionhandle);
|
|
||||||
|
|
||||||
private void RequestMapItemsCompleted(IAsyncResult iar)
|
|
||||||
{
|
{
|
||||||
|
m_log.Warn("[WorldMap]: Starting new remote request thread manually. This means that AvatarEnteringParcel never fired! This needs to be fixed! Don't Mantis this, as the developers can see it in this message");
|
||||||
|
StartThread(new object());
|
||||||
|
}
|
||||||
|
|
||||||
RequestMapItemsDelegate icon = (RequestMapItemsDelegate)iar.AsyncState;
|
RequestMapItems("",remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle);
|
||||||
LLSDMap response = icon.EndInvoke(iar);
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processing thread main() loop for doing remote mapitem requests
|
||||||
|
/// </summary>
|
||||||
|
public void process()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
MapRequestState st = requests.Dequeue();
|
||||||
|
|
||||||
|
// end gracefully
|
||||||
|
if (st.agentID == UUID.Zero)
|
||||||
|
{
|
||||||
|
ThreadTracker.Remove(mapItemReqThread);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
LLSDMap response = RequestMapItemsAsync("", st.agentID, st.flags, st.EstateID, st.godlike, st.itemtype, st.regionhandle);
|
||||||
|
RequestMapItemsCompleted(response);
|
||||||
|
}
|
||||||
|
threadrunning = false;
|
||||||
|
m_log.Warn("[WorldMap]: Remote request thread exiting");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueues the map item request into the processing thread
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state"></param>
|
||||||
|
public void EnqueueMapItemRequest(MapRequestState state)
|
||||||
|
{
|
||||||
|
requests.Enqueue(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the mapitem response to the IClientAPI
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response">The LLSDMap Response for the mapitem</param>
|
||||||
|
private void RequestMapItemsCompleted(LLSDMap response)
|
||||||
|
{
|
||||||
|
|
||||||
UUID requestID = response["requestID"].AsUUID();
|
UUID requestID = response["requestID"].AsUUID();
|
||||||
|
|
||||||
|
@ -364,18 +455,98 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueue the MapItem request for remote processing
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpserver">blank string, we discover this in the process</param>
|
||||||
|
/// <param name="id">Agent ID that we are making this request on behalf</param>
|
||||||
|
/// <param name="flags">passed in from packet</param>
|
||||||
|
/// <param name="EstateID">passed in from packet</param>
|
||||||
|
/// <param name="godlike">passed in from packet</param>
|
||||||
|
/// <param name="itemtype">passed in from packet</param>
|
||||||
|
/// <param name="regionhandle">Region we're looking up</param>
|
||||||
public void RequestMapItems(string httpserver, UUID id, uint flags,
|
public void RequestMapItems(string httpserver, UUID id, uint flags,
|
||||||
uint EstateID, bool godlike, uint itemtype, ulong regionhandle)
|
uint EstateID, bool godlike, uint itemtype, ulong regionhandle)
|
||||||
{
|
{
|
||||||
//m_log.Info("[INTER]: " + debugRegionName + ": SceneCommunicationService: Sending InterRegion Notification that region is up " + region.RegionName);
|
MapRequestState st = new MapRequestState();
|
||||||
RequestMapItemsDelegate d = RequestMapItemsAsync;
|
st.agentID = id;
|
||||||
d.BeginInvoke(httpserver, id,flags,EstateID,godlike,itemtype,regionhandle,RequestMapItemsCompleted, d);
|
st.flags = flags;
|
||||||
//bool val = m_commsProvider.InterRegion.RegionUp(new SerializableRegionInfo(region));
|
st.EstateID = EstateID;
|
||||||
|
st.godlike = godlike;
|
||||||
|
st.itemtype = itemtype;
|
||||||
|
st.regionhandle = regionhandle;
|
||||||
|
EnqueueMapItemRequest(st);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does the actual remote mapitem request
|
||||||
|
/// This should be called from an asynchronous thread
|
||||||
|
/// Request failures get blacklisted until region restart so we don't
|
||||||
|
/// continue to spend resources trying to contact regions that are down.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpserver">blank string, we discover this in the process</param>
|
||||||
|
/// <param name="id">Agent ID that we are making this request on behalf</param>
|
||||||
|
/// <param name="flags">passed in from packet</param>
|
||||||
|
/// <param name="EstateID">passed in from packet</param>
|
||||||
|
/// <param name="godlike">passed in from packet</param>
|
||||||
|
/// <param name="itemtype">passed in from packet</param>
|
||||||
|
/// <param name="regionhandle">Region we're looking up</param>
|
||||||
|
/// <returns></returns>
|
||||||
private LLSDMap RequestMapItemsAsync(string httpserver, UUID id, uint flags,
|
private LLSDMap RequestMapItemsAsync(string httpserver, UUID id, uint flags,
|
||||||
uint EstateID, bool godlike, uint itemtype, ulong regionhandle)
|
uint EstateID, bool godlike, uint itemtype, ulong regionhandle)
|
||||||
{
|
{
|
||||||
|
bool blacklisted = false;
|
||||||
|
lock (m_blacklistedregions)
|
||||||
|
{
|
||||||
|
if (m_blacklistedregions.ContainsKey(regionhandle))
|
||||||
|
blacklisted = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blacklisted)
|
||||||
|
return new LLSDMap();
|
||||||
|
|
||||||
UUID requestID = UUID.Random();
|
UUID requestID = UUID.Random();
|
||||||
|
lock (m_cachedRegionMapItemsAddress)
|
||||||
|
{
|
||||||
|
if (m_cachedRegionMapItemsAddress.ContainsKey(regionhandle))
|
||||||
|
httpserver = m_cachedRegionMapItemsAddress[regionhandle];
|
||||||
|
}
|
||||||
|
if (httpserver.Length == 0)
|
||||||
|
{
|
||||||
|
RegionInfo mreg = m_scene.SceneGridService.RequestNeighbouringRegionInfo(regionhandle);
|
||||||
|
if (mreg != null)
|
||||||
|
{
|
||||||
|
httpserver = "http://" + mreg.ExternalEndPoint.Address.ToString() + ":" + mreg.HttpPort + "/MAP/MapItems/" + regionhandle.ToString();
|
||||||
|
lock (m_cachedRegionMapItemsAddress)
|
||||||
|
{
|
||||||
|
if (!m_cachedRegionMapItemsAddress.ContainsKey(regionhandle))
|
||||||
|
m_cachedRegionMapItemsAddress.Add(regionhandle, httpserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lock (m_blacklistedregions)
|
||||||
|
{
|
||||||
|
if (!m_blacklistedregions.ContainsKey(regionhandle))
|
||||||
|
m_blacklistedregions.Add(regionhandle, System.Environment.TickCount);
|
||||||
|
}
|
||||||
|
m_log.WarnFormat("[WorldMap]: Blacklisted region {0}", regionhandle.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blacklisted = false;
|
||||||
|
lock (m_blacklistedurls)
|
||||||
|
{
|
||||||
|
if (m_blacklistedurls.ContainsKey(httpserver))
|
||||||
|
blacklisted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't find the http server
|
||||||
|
if (httpserver.Length == 0 || blacklisted)
|
||||||
|
return new LLSDMap();
|
||||||
|
|
||||||
MapRequestState mrs = new MapRequestState();
|
MapRequestState mrs = new MapRequestState();
|
||||||
mrs.agentID = id;
|
mrs.agentID = id;
|
||||||
|
@ -413,30 +584,43 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
|
||||||
{
|
{
|
||||||
m_log.InfoFormat("[WorldMap] Bad send on GetMapItems {0}", ex.Message);
|
m_log.InfoFormat("[WorldMap] Bad send on GetMapItems {0}", ex.Message);
|
||||||
responseMap["connect"] = LLSD.FromBoolean(false);
|
responseMap["connect"] = LLSD.FromBoolean(false);
|
||||||
|
lock (m_blacklistedurls)
|
||||||
|
{
|
||||||
|
if (!m_blacklistedurls.ContainsKey(httpserver))
|
||||||
|
m_blacklistedurls.Add(httpserver, System.Environment.TickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_log.WarnFormat("[WorldMap]: Blacklisted {0}", httpserver);
|
||||||
|
|
||||||
return responseMap;
|
return responseMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
//m_log.Info("[OGP] waiting for a reply after rez avatar send");
|
|
||||||
string response_mapItems_reply = null;
|
string response_mapItems_reply = null;
|
||||||
{ // get the response
|
{ // get the response
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
WebResponse webResponse = mapitemsrequest.GetResponse();
|
WebResponse webResponse = mapitemsrequest.GetResponse();
|
||||||
if (webResponse == null)
|
if (webResponse != null)
|
||||||
{
|
{
|
||||||
//m_log.Info("[OGP:] Null reply on rez_avatar post");
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamReader sr = new StreamReader(webResponse.GetResponseStream());
|
StreamReader sr = new StreamReader(webResponse.GetResponseStream());
|
||||||
response_mapItems_reply = sr.ReadToEnd().Trim();
|
response_mapItems_reply = sr.ReadToEnd().Trim();
|
||||||
//m_log.InfoFormat("[OGP]: rez_avatar reply was {0} ", response_mapItems_reply);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new LLSDMap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (WebException)
|
catch (WebException)
|
||||||
{
|
{
|
||||||
//m_log.InfoFormat("[OGP]: exception on read after send of rez avatar {0}", ex.Message);
|
|
||||||
responseMap["connect"] = LLSD.FromBoolean(false);
|
responseMap["connect"] = LLSD.FromBoolean(false);
|
||||||
|
lock (m_blacklistedurls)
|
||||||
|
{
|
||||||
|
if (!m_blacklistedurls.ContainsKey(httpserver))
|
||||||
|
m_blacklistedurls.Add(httpserver, System.Environment.TickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_log.WarnFormat("[WorldMap]: Blacklisted {0}", httpserver);
|
||||||
|
|
||||||
return responseMap;
|
return responseMap;
|
||||||
}
|
}
|
||||||
|
@ -655,7 +839,34 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
|
||||||
}
|
}
|
||||||
return responsemap;
|
return responsemap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID)
|
||||||
|
{
|
||||||
|
// You may ask, why this is in a threadpool to start with..
|
||||||
|
// The reason is so we don't cause the thread to freeze waiting
|
||||||
|
// for the 1 second it costs to start a thread manually.
|
||||||
|
|
||||||
|
if (!threadrunning)
|
||||||
|
ThreadPool.QueueUserWorkItem(new WaitCallback(this.StartThread));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MakeChildAgent(ScenePresence avatar)
|
||||||
|
{
|
||||||
|
List<ScenePresence> presences = m_scene.GetAvatars();
|
||||||
|
int rootcount = 0;
|
||||||
|
for (int i = 0; i < presences.Count; i++)
|
||||||
|
{
|
||||||
|
if (presences[i] != null)
|
||||||
|
{
|
||||||
|
if (!presences[i].IsChildAgent)
|
||||||
|
rootcount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rootcount <= 1)
|
||||||
|
StopThread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct MapRequestState
|
public struct MapRequestState
|
||||||
{
|
{
|
||||||
public UUID agentID;
|
public UUID agentID;
|
||||||
|
@ -666,5 +877,4 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
|
||||||
public ulong regionhandle;
|
public ulong regionhandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue