diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs index be8a9a2307..4dc9033283 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs @@ -26,6 +26,7 @@ */ using System; using System.Reflection; +using System.Threading; using System.Collections.Generic; using OpenSim.Framework; using OpenSim.Services.Interfaces; @@ -37,54 +38,17 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid { public class RegionInfoCache { - private const double CACHE_EXPIRATION_SECONDS = 300.0; // 5 minutes + private const double CACHE_EXPIRATION_SECONDS = 120; // 2 minutes opensim regions change a lot // private static readonly ILog m_log = // LogManager.GetLogger( // MethodBase.GetCurrentMethod().DeclaringType); - internal struct ScopedRegionUUID - { - public UUID m_scopeID; - public UUID m_regionID; - public ScopedRegionUUID(UUID scopeID, UUID regionID) - { - m_scopeID = scopeID; - m_regionID = regionID; - } - } - - internal struct ScopedRegionName - { - public UUID m_scopeID; - public string m_name; - public ScopedRegionName(UUID scopeID, string name) - { - m_scopeID = scopeID; - m_name = name; - } - } - - internal struct ScopedRegionPosition - { - public UUID m_scopeID; - public ulong m_regionHandle; - public ScopedRegionPosition(UUID scopeID, ulong handle) - { - m_scopeID = scopeID; - m_regionHandle = handle; - } - } - - private ExpiringCache m_UUIDCache; - private ExpiringCache m_NameCache; - private ExpiringCache m_PositionCache; + private RegionsExpiringCache m_Cache; public RegionInfoCache() { - m_UUIDCache = new ExpiringCache(); - m_NameCache = new ExpiringCache(); - m_PositionCache = new ExpiringCache(); + m_Cache = new RegionsExpiringCache(); } public void Cache(GridRegion rinfo) @@ -101,18 +65,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid if (rinfo == null) return; - ScopedRegionUUID id = new ScopedRegionUUID(scopeID,regionID); - - // Cache even null accounts - m_UUIDCache.AddOrUpdate(id, rinfo, CACHE_EXPIRATION_SECONDS); - if (rinfo != null) - { - ScopedRegionName name = new ScopedRegionName(scopeID,rinfo.RegionName); - m_NameCache.AddOrUpdate(name, id, CACHE_EXPIRATION_SECONDS); - - ScopedRegionPosition pos = new ScopedRegionPosition(scopeID, rinfo.RegionHandle); - m_PositionCache.AddOrUpdate(pos, rinfo, CACHE_EXPIRATION_SECONDS); - } + m_Cache.AddOrUpdate(scopeID, rinfo, CACHE_EXPIRATION_SECONDS); } public GridRegion Get(UUID scopeID, UUID regionID, out bool inCache) @@ -120,8 +73,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid inCache = false; GridRegion rinfo = null; - ScopedRegionUUID id = new ScopedRegionUUID(scopeID,regionID); - if (m_UUIDCache.TryGetValue(id, out rinfo)) + if (m_Cache.TryGetValue(scopeID, regionID, out rinfo)) { inCache = true; return rinfo; @@ -135,8 +87,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid inCache = false; GridRegion rinfo = null; - ScopedRegionPosition pos = new ScopedRegionPosition(scopeID, handle); - if (m_PositionCache.TryGetValue(pos, out rinfo)) + if (m_Cache.TryGetValue(scopeID, handle, out rinfo)) { inCache = true; return rinfo; @@ -145,25 +96,450 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid return null; } - public GridRegion Get(UUID scopeID, string name, out bool inCache) { inCache = false; - ScopedRegionName sname = new ScopedRegionName(scopeID,name); - - ScopedRegionUUID id; - if (m_NameCache.TryGetValue(sname, out id)) + GridRegion rinfo = null; + if (m_Cache.TryGetValue(scopeID, name, out rinfo)) { - GridRegion rinfo = null; - if (m_UUIDCache.TryGetValue(id, out rinfo)) - { - inCache = true; - return rinfo; - } + inCache = true; + return rinfo; } return null; } } + + + // following code partialy adapted from lib OpenMetaverse + public class RegionKey : IComparable + { + private UUID m_scopeID; + private UUID m_RegionID; + private DateTime m_expirationDate; + + public RegionKey(UUID scopeID, UUID id) + { + m_scopeID = scopeID; + m_RegionID = id; + } + + public UUID ScopeID + { + get { return m_scopeID; } + } + public DateTime ExpirationDate + { + get { return m_expirationDate; } + set { m_expirationDate = value; } + } + + public int GetHaskCode() + { + int hash = m_scopeID.GetHashCode(); + hash += hash * 23 + m_RegionID.GetHashCode(); + return hash; + } + + public int CompareTo(RegionKey other) + { + return GetHashCode().CompareTo(other.GetHashCode()); + } + } + + public class RegionInfoByScope + { + private Dictionary byname; + private Dictionary byhandle; + + public RegionInfoByScope() + { + byname = new Dictionary(); + byhandle = new Dictionary(); + } + + public RegionInfoByScope(GridRegion region, RegionKey key) + { + byname = new Dictionary(); + byhandle = new Dictionary(); + + byname[region.RegionName] = key; + byhandle[region.RegionHandle] = key; + } + + public void AddRegion(GridRegion region, RegionKey key) + { + if(byname == null) + byname = new Dictionary(); + if(byhandle == null) + byhandle = new Dictionary(); + + byname[region.RegionName] = key; + byhandle[region.RegionHandle] = key; + } + + public void RemoveRegion(GridRegion region) + { + if(byname != null) + byname.Remove(region.RegionName); + if(byhandle != null) + byhandle.Remove(region.RegionHandle); + } + + public void Clear() + { + if(byname != null) + byname.Clear(); + if(byhandle != null) + byhandle.Clear(); + byname = null; + byhandle = null; + } + + public RegionKey get(string name) + { + if(byname == null || !byname.ContainsKey(name)) + return null; + return byname[name]; + } + + public RegionKey get(ulong handle) + { + if(byhandle == null || !byhandle.ContainsKey(handle)) + return null; + return byhandle[handle]; + } + + public int Count() + { + if(byname == null) + return 0; + else + return byname.Count; + } + } + + public sealed class RegionsExpiringCache + { + const double CACHE_PURGE_HZ = 60; + const int MAX_LOCK_WAIT = 5000; // milliseconds + + /// For thread safety + object syncRoot = new object(); + /// For thread safety + object isPurging = new object(); + + Dictionary timedStorage = new Dictionary(); + Dictionary InfobyScope = new Dictionary(); + private System.Timers.Timer timer = new System.Timers.Timer(TimeSpan.FromSeconds(CACHE_PURGE_HZ).TotalMilliseconds); + + public RegionsExpiringCache() + { + timer.Elapsed += PurgeCache; + timer.Start(); + } + + public bool Add(UUID scope, GridRegion region, double expirationSeconds) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + + RegionKey key = new RegionKey(scope , region.RegionID); + + try + { + if (timedStorage.ContainsKey(key)) + return false; + + key.ExpirationDate = DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds); + timedStorage.Add(key, region); + + RegionInfoByScope ris = null; + if(!InfobyScope.TryGetValue(scope, out ris) || ris == null) + { + ris = new RegionInfoByScope(region, key); + InfobyScope[scope] = ris; + } + else + ris.AddRegion(region, key); + + return true; + } + finally { Monitor.Exit(syncRoot);} + } + + public bool AddOrUpdate(UUID scope, GridRegion region, double expirationSeconds) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + + try + { + RegionKey key = new RegionKey(scope, region.RegionID); + key.ExpirationDate = DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds); + + if (timedStorage.ContainsKey(key)) + { + timedStorage.Remove(key); + timedStorage.Add(key, region); + + if(!InfobyScope.ContainsKey(scope)) + { + RegionInfoByScope ris = new RegionInfoByScope(region, key); + InfobyScope[scope] = ris; + } + return false; + } + else + { + timedStorage.Add(key, region); + RegionInfoByScope ris = null; + if(!InfobyScope.TryGetValue(scope, out ris) || ris == null) + { + ris = new RegionInfoByScope(region,key); + InfobyScope[scope] = ris; + } + else + ris.AddRegion(region,key); + return true; + } + } + finally { Monitor.Exit(syncRoot); } + } + + public void Clear() + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + timedStorage.Clear(); + InfobyScope.Clear(); + } + finally { Monitor.Exit(syncRoot); } + } + + public bool Contains(UUID scope, GridRegion region) + { + RegionKey key = new RegionKey(scope, region.RegionID); + return Contains(key); + } + + public bool Contains(RegionKey key) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + return timedStorage.ContainsKey(key); + } + finally { Monitor.Exit(syncRoot); } + } + + public int Count + { + get + { + return timedStorage.Count; + } + } + + public bool Remove(UUID scope, GridRegion region) + { + RegionKey key = new RegionKey(scope, region.RegionID); + + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + if (timedStorage.ContainsKey(key)) + { + RegionInfoByScope ris = null; + if(InfobyScope.TryGetValue(scope, out ris) && ris != null) + { + GridRegion r = timedStorage[key]; + if(r != null) + ris.RemoveRegion(r); + if(ris.Count() == 0) + InfobyScope.Remove(scope); + } + timedStorage.Remove(key); + return true; + } + else + return false; + } + finally { Monitor.Exit(syncRoot); } + } + + public bool TryGetValue(RegionKey key, out GridRegion value) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + if (timedStorage.ContainsKey(key)) + { + value = timedStorage[key]; + return true; + } + } + finally { Monitor.Exit(syncRoot); } + + value = null; + return false; + } + + public bool TryGetValue(UUID scope, UUID id, out GridRegion value) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + RegionKey rk = new RegionKey(scope, id); + if(timedStorage.ContainsKey(rk)) + { + value = timedStorage[rk]; + return true; + } + } + finally { Monitor.Exit(syncRoot); } + + value = null; + return false; + } + + public bool TryGetValue(UUID scope, string name, out GridRegion value) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + value = null; + RegionInfoByScope ris = null; + if(!InfobyScope.TryGetValue(scope, out ris) || ris == null) + return false; + + RegionKey key = ris.get(name); + if(key == null) + return false; + + if(timedStorage.ContainsKey(key)) + { + value = timedStorage[key]; + return true; + } + } + finally { Monitor.Exit(syncRoot); } + + return false; + } + + public bool TryGetValue(UUID scope, ulong handle, out GridRegion value) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + value = null; + RegionInfoByScope ris = null; + if(!InfobyScope.TryGetValue(scope, out ris) || ris == null) + return false; + + RegionKey key = ris.get(handle); + if(key == null) + return false; + + if(timedStorage.ContainsKey(key)) + { + value = timedStorage[key]; + return true; + } + } + finally { Monitor.Exit(syncRoot); } + + value = null; + return false; + } + + public bool Update(UUID scope, GridRegion region, double expirationSeconds) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + + RegionKey key = new RegionKey(scope, region.RegionID); + try + { + if (!timedStorage.ContainsKey(key)) + return false; + + timedStorage.Remove(key); + key.ExpirationDate = DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds); + timedStorage.Add(key, region); + return true; + } + finally { Monitor.Exit(syncRoot); } + } + + /// + /// Purges expired objects from the cache. Called automatically by the purge timer. + /// + private void PurgeCache(object sender, System.Timers.ElapsedEventArgs e) + { + // Only let one thread purge at once - a buildup could cause a crash + // This could cause the purge to be delayed while there are lots of read/write ops + // happening on the cache + if (!Monitor.TryEnter(isPurging)) + return; + + DateTime signalTime = DateTime.UtcNow; + + try + { + // If we fail to acquire a lock on the synchronization root after MAX_LOCK_WAIT, skip this purge cycle + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + return; + try + { + OpenMetaverse.Lazy> expiredItems = new OpenMetaverse.Lazy>(); + + foreach (RegionKey timedKey in timedStorage.Keys) + { + if (timedKey.ExpirationDate < signalTime) + { + // Mark the object for purge + expiredItems.Value.Add(timedKey); + } + else + { + break; + } + } + + + RegionInfoByScope ris; + if (expiredItems.IsValueCreated) + { + foreach (RegionKey key in expiredItems.Value) + { + ris = null; + if(InfobyScope.TryGetValue(key.ScopeID, out ris) && ris != null) + { + GridRegion r = timedStorage[key]; + if(r != null) + ris.RemoveRegion(r); + + if(ris.Count() == 0) + InfobyScope.Remove(key.ScopeID); + } + timedStorage.Remove(key); + } + } + } + finally { Monitor.Exit(syncRoot); } + } + finally { Monitor.Exit(isPurging); } + } + } } diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 6d2e2c8b94..fb35a54e76 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -6394,64 +6394,61 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api { m_host.AddScriptLPS(1); - // edge will be used to pass the Region Coordinates offset - // we want to check for a neighboring sim - LSL_Vector edge = new LSL_Vector(0, 0, 0); + if(dir.x == 0.0 && dir.y == 0.0) + return 1; // SL wiki + + float rsx = World.RegionInfo.RegionSizeX; + float rsy = World.RegionInfo.RegionSizeY; + + // can understand what sl does if position is not in region, so do something :) + float px = (float)Util.Clamp(pos.x, 0.5, rsx - 0.5); + float py = (float)Util.Clamp(pos.y, 0.5, rsy - 0.5); + + float ex, ey; if (dir.x == 0) { - if (dir.y == 0) - { - // Direction vector is 0,0 so return - // false since we're staying in the sim - return 0; - } - else - { - // Y is the only valid direction - edge.y = dir.y / Math.Abs(dir.y) * (World.RegionInfo.RegionSizeY / Constants.RegionSize); - } + ex = px; + ey = dir.y > 0.0 ? rsy + 1.0f : -1.0f; + } + else if(dir.y == 0.0f) + { + ex = dir.x > 0 ? rsx + 1.0f : -1.0f; + ey = py; } else { - LSL_Float mag; - if (dir.x > 0) - { - mag = (World.RegionInfo.RegionSizeX - pos.x) / dir.x; - } + float dx = (float) dir.x; + float dy = (float) dir.y; + + float t1 = dx * dx + dy * dy; + dx /= t1; + dy /= t1; + + if(dx > 0) + t1 = (rsx + 1f - px)/dx; else - { - mag = (pos.x/dir.x); - } + t1 = -(px + 1f)/dx; - mag = Math.Abs(mag); - edge.y = pos.y + (dir.y * mag); - - if (edge.y > World.RegionInfo.RegionSizeY || edge.y < 0) - { - // Y goes out of bounds first - edge.y = dir.y / Math.Abs(dir.y) * (World.RegionInfo.RegionSizeY / Constants.RegionSize); - } + float t2; + if(dy > 0) + t2 = (rsy + 1f - py)/dy; else - { - // X goes out of bounds first or its a corner exit - edge.y = 0; - edge.x = dir.x / Math.Abs(dir.x) * (World.RegionInfo.RegionSizeY / Constants.RegionSize); - } + t2 = -(py + 1f)/dy; + + if(t1 > t2) + t1 = t2; + + ex = px + t1 * dx; + ey = py + t1 * dy; } - List neighbors = World.GridService.GetNeighbours(World.RegionInfo.ScopeID, World.RegionInfo.RegionID); - - uint neighborX = World.RegionInfo.RegionLocX + (uint)edge.x; - uint neighborY = World.RegionInfo.RegionLocY + (uint)edge.y; - - foreach (GridRegion sri in neighbors) - { - if (sri.RegionCoordX == neighborX && sri.RegionCoordY == neighborY) - return 0; - } + ex += World.RegionInfo.WorldLocX; + ey += World.RegionInfo.WorldLocY; + if(World.GridService.GetRegionByPosition(World.RegionInfo.ScopeID, (int)ex, (int)ey) != null) + return 0; return 1; }