From 9bae957ccbd9a9d6075fb151b45928966c04c39d Mon Sep 17 00:00:00 2001 From: Vorspire Date: Fri, 21 Aug 2015 23:09:49 +0100 Subject: [PATCH] + Map Updates (4) * Details in-file: Map.cs --- Server/Map.cs | 788 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 763 insertions(+), 25 deletions(-) diff --git a/Server/Map.cs b/Server/Map.cs index 9119916ff..e54a62ef7 100644 --- a/Server/Map.cs +++ b/Server/Map.cs @@ -18,14 +18,119 @@ * ***************************************************************************/ +#region Map Updates + +/* + * Name: Map Updates + * Date: 20th August, 2015 + * Author: Vorspire + * Testing: Punkte + * + * Test Methods: Stealth; 250+ clients connected, all moving randomly. + * Test Results: 35ms average latency (ping) under load. + * + * Notes: + * Until these updates become main-stream, they will remain differentiated + * by their preprocessor directives. When they are deemed stable enough, + * old code can be removed, along with the directives. + */ + +/* + * Map_AllUpdates + * + * When defined, enables all updates listed below regardless of whether their + * preprocessor directives are defined. + * + * This can be used to compile your server with all of the updates enabled by + * adding a single preprocessor directive definition to your build solution. + */ +//#define Map_AllUpdates + +/* + * Map_NewEnumerables + * + * When defined, enables a major update to the IPooledEnumerables factory. + * + * This update removes the need for enumerator instantiation and replaces + * them with simple, yet powerful Linq queries. + * + * In addition, the PooledEnumerable class is replaced with a compatible, + * single generic class template and takes advantage of the nature of + * nested static context to ensure that a buffer pool is available for + * each type of PooledEnumerable result, where result is T. + * This update generally increases performance and reduces overall player + * connection latency. + */ +//#define Map_NewEnumerables + +/* + * UseMaxRange + * + * When defined, enables a minor update that forces Get*InRange methods to + * use Core.GlobalMaxUpdateRange, when no range is specified. + * + * By default, a constant range of 18 is used however, Core.GlobalMaxUpdateRange + * is usually greater than that with a default value of 24. + * + * This update will allow things such as Effects to be displayed to more players, + * as well as increasing the range of player sight. + * + * The benefits of this update appeal to players who choose to increase the + * dimensions of their game window beyond the client's limits. + * (This can also be beneficial for shards that mainly target the Enhanced client) + */ +//#define Map_UseMaxRange + +/* + * Map_PoolFixColumn + * + * When defined, enables aminor update that attempts to improve the performance + * of Item stack fixing. + * + * Item stack fixing is a feature that corrects the Z level of items that + * are stacked on a single tile. + * + * This update also uses linq to increase performance. + */ +//#define Map_PoolFixColumn + +/* + * Map_InternalProtection + * + * When defined, enables a minor update that protects the Internal Map from + * potential name changes and ensures that Maps can be correctly parsed by + * ID or Name without conflicts. + * + * In some cases where the AllMaps list is modified after all Maps have been + * defined, the Map names may be cached and that cache will become stale. + * This update removes the caching and uses Linq to improve performance. + * + * If you have issues with Map parsing where the Map returns null or an + * unexpected Map instance, try enabling this update. + * + * If your shard implements any kind of feature that modifies (adds or removes) + * the AllMaps list, then you should enable this update. + * If this update is not enabled in the case of the above context, issues can + * be verified by having your system generate a new Map instance and modifying + * the AllMaps list, then using [Props on an Item or Mobile and selecting the + * Map property from the Gump; The list of names may not be what you expect, + * in which case, enabling this update will fix it. + */ +//#define Map_InternalProtection + +#endregion + using System; using System.Collections; using System.Collections.Generic; -using System.Runtime.Serialization; using Server.Items; using Server.Network; using Server.Targeting; +#if Map_NewEnumerables || Map_PoolFixColumn || Map_InternalProtection || Map_AllUpdates +using System.Linq; +#endif + namespace Server { [Flags] @@ -51,10 +156,265 @@ namespace Server void Free(); } +#if Map_NewEnumerables || Map_AllUpdates + public static class PooledEnumeration + { + public delegate IEnumerable Selector(Sector sector, Rectangle2D bounds); + + public static Selector ClientSelector { get; set; } + public static Selector EntitySelector { get; set; } + public static Selector MobileSelector { get; set; } + public static Selector ItemSelector { get; set; } + public static Selector MultiSelector { get; set; } + public static Selector MultiTileSelector { get; set; } + + static PooledEnumeration() + { + ClientSelector = SelectClients; + EntitySelector = SelectEntities; + MobileSelector = SelectMobiles; + ItemSelector = SelectItems; + MultiSelector = SelectMultis; + MultiTileSelector = SelectMultiTiles; + } + + public static IEnumerable SelectClients(Sector s, Rectangle2D bounds) + { + return s.Clients.Where(o => o != null && o.Mobile != null && !o.Mobile.Deleted && bounds.Contains(o.Mobile)); + } + + public static IEnumerable SelectEntities(Sector s, Rectangle2D bounds) + { + return + Enumerable.Empty() + .Union(s.Mobiles.Where(o => o != null && !o.Deleted)) + .Union(s.Items.Where(o => o != null && !o.Deleted && o.Parent == null)) + .Where(bounds.Contains); + } + + public static IEnumerable SelectMobiles(Sector s, Rectangle2D bounds) + { + return s.Mobiles.Where(o => o != null && !o.Deleted && bounds.Contains(o)); + } + + public static IEnumerable SelectItems(Sector s, Rectangle2D bounds) + { + return s.Items.Where(o => o != null && !o.Deleted && o.Parent == null && bounds.Contains(o)); + } + + public static IEnumerable SelectMultis(Sector s, Rectangle2D bounds) + { + return s.Multis.Where(o => o != null && !o.Deleted && bounds.Contains(o.Location)); + } + + public static IEnumerable SelectMultiTiles(Sector s, Rectangle2D bounds) + { + foreach (var o in s.Multis.Where(o => o != null && !o.Deleted)) + { + var c = o.Components; + + int x, y, xo, yo; + StaticTile[] t, r; + + for (x = bounds.Start.X; x < bounds.End.X; x++) + { + xo = x - (o.X + c.Min.X); + + if (xo < 0 || xo >= c.Width) + { + continue; + } + + for (y = bounds.Start.Y; y < bounds.End.Y; y++) + { + yo = y - (o.Y + c.Min.Y); + + if (yo < 0 || yo >= c.Height) + { + continue; + } + + t = c.Tiles[xo][yo]; + + if (t.Length <= 0) + { + continue; + } + + r = new StaticTile[t.Length]; + + for (var i = 0; i < t.Length; i++) + { + r[i] = t[i]; + r[i].Z += o.Z; + } + + yield return r; + } + } + } + } + + public static Map.PooledEnumerable GetClients(Map map, Rectangle2D bounds) + { + return Map.PooledEnumerable.Instantiate(map, bounds, ClientSelector ?? SelectClients); + } + + public static Map.PooledEnumerable GetEntities(Map map, Rectangle2D bounds) + { + return Map.PooledEnumerable.Instantiate(map, bounds, EntitySelector ?? SelectEntities); + } + + public static Map.PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) + { + return Map.PooledEnumerable.Instantiate(map, bounds, MobileSelector ?? SelectMobiles); + } + + public static Map.PooledEnumerable GetItems(Map map, Rectangle2D bounds) + { + return Map.PooledEnumerable.Instantiate(map, bounds, ItemSelector ?? SelectItems); + } + + public static Map.PooledEnumerable GetMultis(Map map, Rectangle2D bounds) + { + return Map.PooledEnumerable.Instantiate(map, bounds, MultiSelector ?? SelectMultis); + } + + public static Map.PooledEnumerable GetMultiTiles(Map map, Rectangle2D bounds) + { + return Map.PooledEnumerable.Instantiate(map, bounds, MultiTileSelector ?? SelectMultiTiles); + } + + public static IEnumerable EnumerateSectors(Map map, Rectangle2D bounds) + { + if (map == null || map == Map.Internal) + { + yield break; + } + + int x1 = bounds.Start.X, y1 = bounds.Start.Y, x2 = bounds.End.X, y2 = bounds.End.Y, xSector, ySector; + + if (!Bound(map, ref x1, ref y1, ref x2, ref y2, out xSector, out ySector)) + { + yield break; + } + + Sector s; + + var index = 0; + + while (NextSector(map, x1, y1, x2, y2, ref index, ref xSector, ref ySector, out s)) + { + yield return s; + } + } + + public static bool Bound( + Map map, + ref int x1, + ref int y1, + ref int x2, + ref int y2, + out int xSector, + out int ySector) + { + if (map == null || map == Map.Internal) + { + xSector = ySector = 0; + return false; + } + + map.Bound(x1, y1, out x1, out y1); + map.Bound(x2 - 1, y2 - 1, out x2, out y2); + + x1 >>= Map.SectorShift; + y1 >>= Map.SectorShift; + x2 >>= Map.SectorShift; + y2 >>= Map.SectorShift; + + xSector = x1; + ySector = y1; + + return true; + } + + private static bool NextSector( + Map map, + int x1, + int y1, + int x2, + int y2, + ref int index, + ref int xSector, + ref int ySector, + out Sector s) + { + if (map == null) + { + s = null; + xSector = ySector = 0; + return false; + } + + if (map == Map.Internal) + { + s = map.InvalidSector; + xSector = ySector = 0; + return false; + } + + if (index++ > 0) + { + if (++ySector > y2) + { + ySector = y1; + + if (++xSector > x2) + { + xSector = x1; + + s = map.InvalidSector; + return false; + } + } + } + + s = map.GetRealSector(xSector, ySector); + return true; + } + } +#endif + [Parsable] //[CustomEnum( new string[]{ "Felucca", "Trammel", "Ilshenar", "Malas", "Internal" } )] public sealed class Map : IComparable, IComparable { + #region Compile-Time -> Run-Time Support +#if Map_NewEnumerables || Map_AllUpdates + public static readonly bool NewEnumerables = true; +#else + public static readonly bool NewEnumerables = false; +#endif + +#if Map_UseMaxRange || Map_AllUpdates + public static readonly bool UseMaxRange = true; +#else + public static readonly bool UseMaxRange = false; +#endif + +#if Map_PoolFixColumn || Map_AllUpdates + public static readonly bool PoolFixColumn = true; +#else + public static readonly bool PoolFixColumn = false; +#endif + +#if Map_InternalProtection || Map_AllUpdates + public static readonly bool InternalProtection = true; +#else + public static readonly bool InternalProtection = false; +#endif + #endregion + public const int SectorSize = 16; public const int SectorShift = 4; public static int SectorActiveRange = 2; @@ -92,6 +452,49 @@ namespace Server private TileMatrix m_Tiles; +#if Map_InternalProtection || Map_AllUpdates + public static string[] GetMapNames() + { + return m_Maps.Where(m => m != null).Select(m => m.Name).ToArray(); + } + + public static Map[] GetMapValues() + { + return m_Maps.Where(m => m != null).ToArray(); + } + + public static Map Parse(string value) + { + if (String.IsNullOrWhiteSpace(value)) + { + return null; + } + + if (Insensitive.Equals(value, "Internal")) + { + return Internal; + } + + int index; + + if (!Int32.TryParse(value, out index)) + { + return m_Maps.FirstOrDefault(m => m != null && Insensitive.Equals(m.Name, value)); + } + + if (index == 127) + { + return Internal; + } + + return m_Maps.FirstOrDefault(m => m != null && m.MapIndex == index); + } + + public override string ToString() + { + return Name; + } +#else private static string[] m_MapNames; private static Map[] m_MapValues; @@ -149,6 +552,7 @@ namespace Server { return m_Name; } +#endif public int GetAverageZ( int x, int y ) { @@ -198,13 +602,98 @@ namespace Server return ( v / 2 ); } +#if Map_NewEnumerables || Map_AllUpdates #region Get*InRange/Bounds public IPooledEnumerable GetObjectsInRange(Point3D p) { - if ( this == Map.Internal ) - return NullEnumerable.Instance; +#if Map_UseMaxRange || Map_AllUpdates + return GetObjectsInRange(p, Core.GlobalMaxUpdateRange); +#else + return GetObjectsInRange(p, 18); +#endif + } - return PooledEnumerable.Instantiate(EntityEnumerator.Instantiate(this, new Rectangle2D(p.m_X - 18, p.m_Y - 18, 37, 37))); + public IPooledEnumerable GetObjectsInRange(Point3D p, int range) + { + return GetObjectsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + } + + public IPooledEnumerable GetObjectsInBounds(Rectangle2D bounds) + { + return PooledEnumeration.GetEntities(this, bounds); + } + + public IPooledEnumerable GetClientsInRange(Point3D p) + { +#if Map_UseMaxRange || Map_AllUpdates + return GetClientsInRange(p, Core.GlobalMaxUpdateRange); +#else + return GetClientsInRange(p, 18); +#endif + } + + public IPooledEnumerable GetClientsInRange(Point3D p, int range) + { + return GetClientsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + } + + public IPooledEnumerable GetClientsInBounds(Rectangle2D bounds) + { + return PooledEnumeration.GetClients(this, bounds); + } + + public IPooledEnumerable GetItemsInRange(Point3D p) + { +#if Map_UseMaxRange || Map_AllUpdates + return GetItemsInRange(p, Core.GlobalMaxUpdateRange); +#else + return GetItemsInRange(p, 18); +#endif + } + + public IPooledEnumerable GetItemsInRange(Point3D p, int range) + { + return GetItemsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + } + + public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) + { + return PooledEnumeration.GetItems(this, bounds); + } + + public IPooledEnumerable GetMobilesInRange(Point3D p) + { +#if Map_UseMaxRange || Map_AllUpdates + return GetMobilesInRange(p, Core.GlobalMaxUpdateRange); +#else + return GetMobilesInRange(p, 18); +#endif + } + + public IPooledEnumerable GetMobilesInRange(Point3D p, int range) + { + return GetMobilesInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + } + + public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) + { + return PooledEnumeration.GetMobiles(this, bounds); + } + #endregion + + public IPooledEnumerable GetMultiTilesAt(int x, int y) + { + return PooledEnumeration.GetMultiTiles(this, new Rectangle2D(x, y, 1, 1)); + } +#else + #region Get*InRange/Bounds + public IPooledEnumerable GetObjectsInRange(Point3D p) + { +#if Map_UseMaxRange || Map_AllUpdates + return GetObjectsInRange(p, Core.GlobalMaxUpdateRange); +#else + return GetObjectsInRange(p, 18); +#endif } public IPooledEnumerable GetObjectsInRange(Point3D p, int range) @@ -225,10 +714,11 @@ namespace Server public IPooledEnumerable GetClientsInRange(Point3D p) { - if ( this == Map.Internal ) - return NullEnumerable.Instance; - - return PooledEnumerable.Instantiate(ClientEnumerator.Instantiate(this, new Rectangle2D(p.m_X - 18, p.m_Y - 18, 37, 37))); +#if Map_UseMaxRange || Map_AllUpdates + return GetClientsInRange(p, Core.GlobalMaxUpdateRange); +#else + return GetClientsInRange(p, 18); +#endif } public IPooledEnumerable GetClientsInRange(Point3D p, int range) @@ -249,10 +739,11 @@ namespace Server public IPooledEnumerable GetItemsInRange(Point3D p) { - if ( this == Map.Internal ) - return NullEnumerable.Instance; - - return PooledEnumerable.Instantiate(ItemEnumerator.Instantiate(this, new Rectangle2D(p.m_X - 18, p.m_Y - 18, 37, 37))); +#if Map_UseMaxRange || Map_AllUpdates + return GetItemsInRange(p, Core.GlobalMaxUpdateRange); +#else + return GetItemsInRange(p, 18); +#endif } public IPooledEnumerable GetItemsInRange(Point3D p, int range) @@ -273,10 +764,11 @@ namespace Server public IPooledEnumerable GetMobilesInRange(Point3D p) { - if ( this == Map.Internal ) - return NullEnumerable.Instance; - - return PooledEnumerable.Instantiate(MobileEnumerator.Instantiate(this, new Rectangle2D(p.m_X - 18, p.m_Y - 18, 37, 37))); +#if Map_UseMaxRange || Map_AllUpdates + return GetMobilesInRange(p, Core.GlobalMaxUpdateRange); +#else + return GetMobilesInRange(p, 18); +#endif } public IPooledEnumerable GetMobilesInRange(Point3D p, int range) @@ -308,6 +800,7 @@ namespace Server return PooledEnumerable.Instantiate(MultiTileEnumerator.Instantiate(sector, new Point2D(x, y))); } +#endif #region CanFit public bool CanFit( Point3D p, int height, bool checkBlocksFit ) @@ -455,6 +948,141 @@ namespace Server } } +#if Map_PoolFixColumn || Map_AllUpdates + private static readonly Queue> _FixPool = new Queue>(128); + + private static readonly List _EmptyFixItems = new List(); + + private static List AcquireFixItems(Map map, int x, int y) + { + if (map == null || map == Internal || x < 0 || x > map.Width || y < 0 || y > map.Height) + { + return _EmptyFixItems; + } + + List pool = null; + + lock (_FixPool) + { + if (_FixPool.Count > 0) + { + pool = _FixPool.Dequeue(); + } + } + + if (pool == null) + { + pool = new List(128); // Arbitrary limit + } + + var eable = map.GetItemsInRange(new Point3D(x, y, 0), 0); + + pool.AddRange( + eable.Where(item => item.ItemID <= TileData.MaxItemValue && !(item is BaseMulti)) + .OrderBy(item => item.Z) + .Take(pool.Capacity)); + + eable.Free(); + + return pool; + } + + private static void FreeFixItems(List pool) + { + if (pool == _EmptyFixItems) + { + return; + } + + pool.Clear(); + + lock (_FixPool) + { + if (_FixPool.Count < 128) + { + _FixPool.Enqueue(pool); + } + } + } + + public void FixColumn(int x, int y) + { + var landTile = Tiles.GetLandTile(x, y); + var tiles = Tiles.GetStaticTiles(x, y, true); + + int landZ = 0, landAvg = 0, landTop = 0; + GetAverageZ(x, y, ref landZ, ref landAvg, ref landTop); + + var items = AcquireFixItems(this, x, y); + + for (var i = 0; i < items.Count; i++) + { + var toFix = items[i]; + + if (!toFix.Movable) + { + continue; + } + + var z = int.MinValue; + var currentZ = toFix.Z; + + if (!landTile.Ignored && landAvg <= currentZ) + { + z = landAvg; + } + + foreach (var tile in tiles) + { + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + var checkZ = tile.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop == checkZ && !id.Surface) + { + ++checkTop; + } + + if (checkTop > z && checkTop <= currentZ) + { + z = checkTop; + } + } + + for (var j = 0; j < items.Count; ++j) + { + if (j == i) + { + continue; + } + + var item = items[j]; + var id = item.ItemData; + + var checkZ = item.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop == checkZ && !id.Surface) + { + ++checkTop; + } + + if (checkTop > z && checkTop <= currentZ) + { + z = checkTop; + } + } + + if (z != int.MinValue) + { + toFix.Location = new Point3D(toFix.X, toFix.Y, z); + } + } + + FreeFixItems(items); + } +#else public void FixColumn( int x, int y ) { LandTile landTile = Tiles.GetLandTile( x, y ); @@ -536,6 +1164,7 @@ namespace Server toFix.Location = new Point3D( toFix.X, toFix.Y, z ); } } +#endif /* This could be probably be re-implemented if necessary (perhaps via an ITile interface?). public List GetTilesAt( Point2D p, bool items, bool land, bool statics ) @@ -594,7 +1223,6 @@ namespace Server } } - StaticTile[] staticTiles = Tiles.GetStaticTiles( p.X, p.Y, true ); for ( int i = 0; i < staticTiles.Length; i++ ) @@ -616,8 +1244,7 @@ namespace Server } } } - - + Sector sector = GetSector( p.X, p.Y ); for ( int i = 0; i < sector.Items.Count; i++ ) @@ -644,7 +1271,6 @@ namespace Server } } - return surface; } @@ -939,12 +1565,11 @@ namespace Server { get { - if (m_Tiles == null) - lock (tileLock) - if (m_Tiles == null) - m_Tiles = new TileMatrix(this, m_FileIndex, m_MapID, m_Width, m_Height); + if (m_Tiles != null) + return m_Tiles; - return m_Tiles; + lock (tileLock) + return m_Tiles ?? (m_Tiles = new TileMatrix(this, m_FileIndex, m_MapID, m_Width, m_Height)); } } @@ -1048,14 +1673,126 @@ namespace Server { get { +#if Map_InternalProtection || Map_AllUpdates + if (this == Internal && m_Name != "Internal") + { + Console.WriteLine("Internal Map Name was changed to '{0}'", m_Name); + + m_Name = "Internal"; + } +#endif return m_Name; } set { +#if Map_InternalProtection || Map_AllUpdates + if (this == Internal && value != "Internal") + { + Console.WriteLine("Attempted to set Internal Map Name to '{0}'", value); + + value = "Internal"; + } +#endif m_Name = value; } } +#if Map_NewEnumerables || Map_AllUpdates + public class NullEnumerable : IPooledEnumerable + { + public static readonly NullEnumerable Instance = new NullEnumerable(); + + private readonly IEnumerable _Empty; + + private NullEnumerable() + { + _Empty = Enumerable.Empty(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _Empty.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return _Empty.GetEnumerator(); + } + + public void Free() + { } + } + + public sealed class PooledEnumerable : IPooledEnumerable, IDisposable + { + private static readonly Queue> _Buffer = new Queue>(0x200); + + public static PooledEnumerable Instantiate(Map map, Rectangle2D bounds, PooledEnumeration.Selector selector) + { + PooledEnumerable e = null; + + lock (((ICollection)_Buffer).SyncRoot) + { + if (_Buffer.Count > 0) + { + e = _Buffer.Dequeue(); + } + } + + var pool = PooledEnumeration.EnumerateSectors(map, bounds).SelectMany(s => selector(s, bounds)); + + if (e != null) + { + e._Pool = pool; + } + else + { + e = new PooledEnumerable(pool); + } + + return e; + } + + private bool _IsDisposed; + private IEnumerable _Pool; + + public PooledEnumerable(IEnumerable pool) + { + _Pool = pool; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _Pool.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return _Pool.GetEnumerator(); + } + + public void Free() + { + if (_IsDisposed) + { + return; + } + + _Pool = null; + + lock (((ICollection)_Buffer).SyncRoot) + { + _Buffer.Enqueue(this); + } + } + + public void Dispose() + { + _IsDisposed = true; + _Pool = null; + } + } +#else #region Enumerables public class NullEnumerable : IPooledEnumerable { @@ -1768,6 +2505,7 @@ namespace Server } } #endregion +#endif public Point3D GetPoint( object o, bool eye ) {