/* * 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.Threading; using System.Runtime.InteropServices; using System.Collections.Generic; using OpenSim.Framework; using OpenMetaverse; using log4net; using GridRegion = OpenSim.Services.Interfaces.GridRegion; using Timer = System.Threading.Timer; using ReaderWriterLockSlim = System.Threading.ReaderWriterLockSlim; namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid { public class RegionInfoCache { // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private const int CACHE_EXPIRATION_SECONDS = 120; // 2 minutes opensim regions change a lot private const int CACHE_PURGE_TIME = 60000; // milliseconds public const ulong HANDLEMASK = 0xffffff00ffffff00ul; public const ulong HANDLECOORDMASK = 0xffffff00ul; private static readonly object m_creationLock = new object(); private static readonly Dictionary m_expireControl = new Dictionary(); private static readonly Dictionary m_byHandler = new Dictionary(); private static readonly Dictionary m_byName = new Dictionary(); private static readonly Dictionary m_byUUID = new Dictionary(); // includes handles to the inside of large regions private static readonly Dictionary m_innerHandles = new Dictionary(); //private static bool disposed; private static ReaderWriterLockSlim m_rwLock; private static Timer m_timer; private static double starttimeS; public RegionInfoCache() { lock(m_creationLock) { if(m_rwLock == null) { starttimeS = Util.GetTimeStamp(); m_rwLock = new ReaderWriterLockSlim(); if (m_timer == null) m_timer = new Timer(PurgeCache, null, CACHE_PURGE_TIME, Timeout.Infinite); } } } /* not if static ~RegionInfoCache() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!disposed) { disposed = true; if (m_rwLock != null) { m_rwLock.Dispose(); m_rwLock = null; } if (m_timer != null) { m_timer.Dispose(); m_timer = null; } } } */ public bool AddOrUpdate(GridRegion rinfo) { return AddOrUpdate(rinfo, CACHE_EXPIRATION_SECONDS); } public bool AddOrUpdate(GridRegion rinfo, int expire) { //if (rinfo == null || disposed) if (rinfo == null) return false; bool gotLock = false; try { try { } finally { m_rwLock.EnterWriteLock(); gotLock = true; } int newexpire = (int)(Util.GetTimeStamp() - starttimeS) + expire; ulong handle = rinfo.RegionHandle & HANDLEMASK; if (m_expireControl.ContainsKey(handle)) { if (m_expireControl[handle] < newexpire) m_expireControl[handle] = newexpire; } else { m_expireControl[handle] = newexpire; } if (m_innerHandles.TryGetValue(handle, out GridRegion oldr)) removeFromInner(oldr); addToInner(rinfo); m_byHandler[handle] = rinfo; m_byName[rinfo.RegionName.ToLowerInvariant()] = rinfo; m_byUUID[rinfo.RegionID] = rinfo; return true; } finally { if (gotLock) m_rwLock.ExitWriteLock(); } } public void Cache(GridRegion rinfo) { AddOrUpdate(rinfo, CACHE_EXPIRATION_SECONDS); } public void Cache(GridRegion rinfo, int expireSeconds) { AddOrUpdate(rinfo, expireSeconds); } public void Cache(GridRegion rinfo, float expireSeconds) { AddOrUpdate(rinfo, (int)expireSeconds); } public void Cache(UUID scopeID, GridRegion rinfo) { AddOrUpdate(rinfo, CACHE_EXPIRATION_SECONDS); } public void CacheLocal(GridRegion rinfo) { AddOrUpdate(rinfo, 100000); } public void CacheNearNeighbour(UUID scopeID, GridRegion rinfo) { AddOrUpdate(rinfo, CACHE_EXPIRATION_SECONDS); } public void Cache(UUID scopeID, GridRegion rinfo, int expireSeconds) { AddOrUpdate(rinfo, expireSeconds); } public void Cache(UUID scopeID, GridRegion rinfo, float expireSeconds) { AddOrUpdate(rinfo, (int)expireSeconds); } public void Clear() { //if (disposed) // return; bool gotLock = false; try { try { } finally { m_rwLock.EnterWriteLock(); gotLock = true; } m_expireControl.Clear(); m_byHandler.Clear(); m_byName.Clear(); m_byUUID.Clear(); m_innerHandles.Clear(); } finally { if (gotLock) m_rwLock.ExitWriteLock(); } } public bool Contains(ulong handle) { //if (disposed) // return false; bool gotLock = false; try { try { } finally { m_rwLock.EnterReadLock(); gotLock = true; } return m_byHandler.ContainsKey(handle & HANDLEMASK); } finally { if (gotLock) m_rwLock.ExitReadLock(); } } public bool Contains(GridRegion rinfo) { //if (disposed) // return false; bool gotLock = false; try { try { } finally { m_rwLock.EnterReadLock(); gotLock = true; } if(!m_byHandler.TryGetValue(rinfo.RegionHandle & HANDLEMASK, out GridRegion rcur)) return false; return rcur.RegionID == rinfo.RegionID && rcur.RegionSizeX == rinfo.RegionSizeX && rcur.RegionSizeY == rinfo.RegionSizeY; } finally { if (gotLock) m_rwLock.ExitReadLock(); } } public bool Contains(UUID scope, ulong handle) { return Contains(handle); } public bool Contains(UUID scope, GridRegion rinfo) { return Contains(rinfo); } public int Count() { //if (disposed) // return 0; bool gotLock = false; try { try { } finally { m_rwLock.EnterReadLock(); gotLock = true; } return m_byName.Count; } finally { if (gotLock) m_rwLock.ExitReadLock(); } } public void Remove(GridRegion rinfo) { if (rinfo == null) return; bool gotLock = false; try { try { } finally { m_rwLock.EnterWriteLock(); gotLock = true; } m_byName.Remove(rinfo.RegionName.ToLowerInvariant()); m_byUUID.Remove(rinfo.RegionID); ulong handle = rinfo.RegionHandle & HANDLEMASK; m_byHandler.Remove(handle); removeFromInner(rinfo); if (m_expireControl.ContainsKey(handle)) { m_expireControl.Remove(handle); if (m_expireControl.Count == 0) { m_byHandler.Clear(); m_byName.Clear(); m_byUUID.Clear(); m_innerHandles.Clear(); } } } finally { if (gotLock) m_rwLock.ExitWriteLock(); } } public void Remove(ulong regionHandle) { //if(disposed) // return; bool gotLock = false; try { try { } finally { m_rwLock.EnterWriteLock(); gotLock = true; } regionHandle &= HANDLEMASK; if (m_byHandler.TryGetValue(regionHandle, out GridRegion r)) { m_byName.Remove(r.RegionName.ToLowerInvariant()); m_byUUID.Remove(r.RegionID); removeFromInner(r); m_byHandler.Remove(regionHandle); } if (m_expireControl.ContainsKey(regionHandle)) { m_expireControl.Remove(regionHandle); if (m_expireControl.Count == 0) { m_byHandler.Clear(); m_byName.Clear(); m_byUUID.Clear(); m_innerHandles.Clear(); } } } finally { if (gotLock) m_rwLock.ExitWriteLock(); } } public void Remove(UUID scopeID, GridRegion rinfo) { Remove(rinfo); } public void Remove(UUID scopeID, ulong regionHandle) { Remove(regionHandle); } public GridRegion Get(UUID scopeID, UUID regionID, out bool inCache) { inCache = TryGet(regionID, out GridRegion rinfo); return rinfo; } public GridRegion Get(UUID scopeID, ulong handle, out bool inCache) { inCache = TryGet(handle, out GridRegion rinfo); return rinfo; } public GridRegion Get(UUID scopeID, string name, out bool inCache) { inCache = TryGet(name, out GridRegion rinfo); return rinfo; } public GridRegion Get(UUID scopeID, uint x, uint y, out bool inCache) { inCache = TryGet(x, y, out GridRegion rinfo); return rinfo; } public bool TryGet(UUID regionID, out GridRegion rinfo) { /* if (disposed) { rinfo = null; return false; } */ bool gotLock = false; try { try { } finally { m_rwLock.EnterReadLock(); gotLock = true; } return m_byUUID.TryGetValue(regionID, out rinfo); } finally { if (gotLock) m_rwLock.ExitReadLock(); } } public bool TryGet(ulong handle, out GridRegion rinfo) { /* if (disposed) { rinfo = null; return false; } */ bool gotLock = false; try { try { } finally { m_rwLock.EnterReadLock(); gotLock = true; } handle &= HANDLEMASK; if (m_byHandler.TryGetValue(handle, out rinfo)) return true; return m_innerHandles.TryGetValue(handle, out rinfo); } finally { if (gotLock) m_rwLock.ExitReadLock(); } } public bool TryGet(string name, out GridRegion rinfo) { /* if (disposed) { rinfo = null; return false; } */ bool gotLock = false; try { try { } finally { m_rwLock.EnterReadLock(); gotLock = true; } return m_byName.TryGetValue(name.ToLowerInvariant(), out rinfo); } finally { if (gotLock) m_rwLock.ExitReadLock(); } } public bool TryGet(uint x, uint y, out GridRegion rinfo) { /* if (disposed) { rinfo = null; return false; } */ bool gotLock = false; try { try { } finally { m_rwLock.EnterReadLock(); gotLock = true; } ulong handle = x & HANDLECOORDMASK; handle <<= 32; handle |= y & HANDLECOORDMASK; if (m_byHandler.TryGetValue(handle, out rinfo)) return true; if (!m_innerHandles.TryGetValue(handle, out rinfo)) return false; // extra check, possible redundant int test = rinfo.RegionLocX; if (x < test) { rinfo = null; return false; } test += rinfo.RegionSizeX; if (x >= test) { rinfo = null; return false; } test = rinfo.RegionLocY; if (y < test) { rinfo = null; return false; } test += rinfo.RegionSizeY; if (y < test) return true; rinfo = null; return false; } finally { if (gotLock) m_rwLock.ExitReadLock(); } } private void PurgeCache(object ignored) { //if(disposed || m_expireControl.Count == 0) if (m_expireControl.Count == 0) return; bool gotLock = false; try { try { } finally { m_rwLock.EnterWriteLock(); gotLock = true; } int now = (int)(Util.GetTimeStamp() - starttimeS); List toexpire = new List(m_expireControl.Count); foreach (KeyValuePair kvp in m_expireControl) { if (kvp.Value < now) toexpire.Add(kvp.Key); } if (toexpire.Count == 0) return; ulong h; for (int i = 0; i < toexpire.Count; i++) { h = toexpire[i]; if (m_byHandler.TryGetValue(h, out GridRegion r)) { m_byName.Remove(r.RegionName.ToLowerInvariant()); m_byUUID.Remove(r.RegionID); removeFromInner(r); m_byHandler.Remove(h); } m_expireControl.Remove(h); } } finally { if (gotLock) m_rwLock.ExitWriteLock(); if (m_timer != null) m_timer.Change(CACHE_PURGE_TIME, Timeout.Infinite); } } private void addToInner(GridRegion region) { int rsx = region.RegionSizeX; int rsy = region.RegionSizeY; if (rsx < 257 && rsy < 257) return; rsx >>= 8; rsy >>= 8; ulong handle = region.RegionHandle & HANDLEMASK; fastRegionHandle fh = new fastRegionHandle(handle); uint startY = fh.y; for (int i = 0; i < rsx; i++) { for (int j = 0; j < rsy; j++) { m_innerHandles[fh.toHandle()] = region; fh.y += 256; } fh.y = startY; fh.x += 256; } } private void removeFromInner(GridRegion region) { int rsx = region.RegionSizeX; int rsy = region.RegionSizeY; if (rsx < 257 && rsy < 257) return; rsx >>= 8; rsy >>= 8; ulong handle = region.RegionHandle & HANDLEMASK; fastRegionHandle fh = new fastRegionHandle(handle); uint startY = fh.y; for (int i = 0; i < rsx; i++) { for (int j = 0; j < rsy; j++) { m_innerHandles.Remove(fh.toHandle()); fh.y += 256; } fh.y = startY; fh.x += 256; } } } [StructLayout(LayoutKind.Explicit, Size = 8, Pack = 8)] public class fastRegionHandle { [FieldOffset(0)] public ulong handle; [FieldOffset(0)] public uint y; [FieldOffset(4)] public uint x; public fastRegionHandle(ulong h) { handle = h; if (!BitConverter.IsLittleEndian) { x = (uint)(handle >> 32); y = (uint)handle; } y &= 0xffffff00; x &= 0xffffff00; } public fastRegionHandle(uint px, uint py) { y = py & 0xffffff00; x = px & 0xffffff00; } public ulong toHandle() { if (BitConverter.IsLittleEndian) return handle; else return (ulong)x << 32 | y ; } public static bool operator ==(fastRegionHandle value1, fastRegionHandle value2) { return value1.handle == value2.handle; } public static bool operator !=(fastRegionHandle value1, fastRegionHandle value2) { return value1.handle != value2.handle; } public override int GetHashCode() { return handle.GetHashCode(); } public override bool Equals(Object obj) { if(obj == null) return false; fastRegionHandle p = obj as fastRegionHandle; return p.handle == handle; } } }