diff --git a/MCGalaxy/Database/BlockDB/BlockDB.cs b/MCGalaxy/Database/BlockDB/BlockDB.cs index ff1ca1a7d..303037856 100644 --- a/MCGalaxy/Database/BlockDB/BlockDB.cs +++ b/MCGalaxy/Database/BlockDB/BlockDB.cs @@ -36,12 +36,9 @@ namespace MCGalaxy.DB { /// The path of this BlockDB's backing file on disc. public string FilePath { get { return BlockDBFile.FilePath(MapName); } } - - /// Used to synchronise adding to Cache by multiple threads. - internal readonly object CacheLock = new object(); /// In-memory list of recent BlockDB changes. - public FastList Cache = new FastList(); + public BlockDBCache Cache = new BlockDBCache(); /// Whether changes are actually added to the BlockDB. public bool Used; @@ -83,14 +80,12 @@ namespace MCGalaxy.DB { entry.Flags |= BlockDBFlags.OldCustom; entry.OldRaw = oldExt; } - - lock (CacheLock) - Cache.Add(entry); + Cache.Add(ref entry); } public void WriteEntries() { using (IDisposable writeLock = locker.AccquireWriteLock()) { - if (Cache.Count == 0) return; + if (Cache.Head == null) return; ValidateBackingFile(); using (Stream s = File.OpenWrite(FilePath)) { @@ -98,9 +93,7 @@ namespace MCGalaxy.DB { // and 21 bytes were in the file, this sets the position to byte 16 s.Position = s.Length & ~0x0F; BlockDBFile.WriteEntries(s, Cache); - - lock (CacheLock) - Cache = new FastList(); + Cache.Clear(); } } } @@ -124,13 +117,17 @@ namespace MCGalaxy.DB { } void FindInMemoryAt(ushort x, ushort y, ushort z, Action output) { - Vec3U16 dims = Dims; - int count = Cache.Count, index = (y * dims.Z + z) * dims.X + x; - BlockDBEntry[] items = Cache.Items; - - for (int i = 0; i < count; i++) { - if (items[i].Index != index) continue; - output(items[i]); + int index = (y * Dims.Z + z) * Dims.X + x; + BlockDBCacheNode node = Cache.Tail; + while (node != null) { + BlockDBEntry[] entries = node.Entries; + int count = node.Count; + + for (int i = 0; i < count; i++) { + if (entries[i].Index != index) continue; + output(entries[i]); + } + lock (Cache.Locker) node = node.Next; } } @@ -138,16 +135,29 @@ namespace MCGalaxy.DB { /// whether an entry before start time was reached. public bool FindChangesBy(int[] ids, DateTime start, DateTime end, out Vec3U16 dims, Action output) { - long startDelta = (long)start.Subtract(Epoch).TotalSeconds; - long endDelta = (long)end.Subtract(Epoch).TotalSeconds; + int startDelta = ClampDelta(start.Subtract(Epoch)); + int endDelta = ClampDelta(end.Subtract(Epoch)); using (IDisposable readLock = locker.AccquireReadLock()) { dims = Dims; - // Read entries from memory cache - int count = Cache.Count; - BlockDBEntry[] items = Cache.Items; + if (FindInMemoryBy(ids, startDelta, endDelta, output)) return true; + + if (!File.Exists(FilePath)) return false; + using (Stream s = File.OpenRead(FilePath)) { + BlockDBFile.ReadHeader(s, out dims); + return BlockDBFile.FindChangesBy(s, ids, startDelta, endDelta, output); + } + } + } + + bool FindInMemoryBy(int[] ids, int startDelta, int endDelta, Action output) { + BlockDBCacheNode node = Cache.Head; + while (node != null) { + int count = node.Count; + BlockDBEntry[] entries = node.Entries; + for (int i = count - 1; i >= 0; i--) { - BlockDBEntry entry = items[i]; + BlockDBEntry entry = entries[i]; if (entry.TimeDelta < startDelta) return true; if (entry.TimeDelta > endDelta) continue; @@ -156,14 +166,16 @@ namespace MCGalaxy.DB { output(entry); break; } } - - // Read entries from disc cache - if (!File.Exists(FilePath)) return false; - using (Stream s = File.OpenRead(FilePath)) { - BlockDBFile.ReadHeader(s, out dims); - return BlockDBFile.FindChangesBy(s, ids, startDelta, endDelta, output); - } + lock (Cache.Locker) node = node.Prev; } + return false; + } + + static int ClampDelta(TimeSpan delta) { + long secs = (long)delta.TotalSeconds; + if (secs < int.MinValue) return int.MinValue; + if (secs > int.MaxValue) return int.MaxValue; + return (int)secs; } diff --git a/MCGalaxy/Database/BlockDB/BlockDBCache.cs b/MCGalaxy/Database/BlockDB/BlockDBCache.cs index 27ccf1f09..c1036cbfc 100644 --- a/MCGalaxy/Database/BlockDB/BlockDBCache.cs +++ b/MCGalaxy/Database/BlockDB/BlockDBCache.cs @@ -16,17 +16,56 @@ permissions and limitations under the Licenses. */ using System; -using System.Data; -using MCGalaxy.SQL; namespace MCGalaxy.DB { /// Optimised in-memory BlockDB cache. public sealed class BlockDBCache { - public BlockDBCacheNode Head, Tail; + public BlockDBCacheNode Tail, Head; - int nextSize = 10000; + /// Used to synchronise adding to Cache by multiple threads. + public readonly object Locker = new object(); + + public void Add(ref BlockDBEntry entry) { + lock (Locker) { + if (Head == null || Head.Count == Head.Entries.Length) + AddNextNode(); + + Head.Entries[Head.Count] = entry; Head.Count++; + } + } + + public void Clear() { + lock (Locker) { + if (Tail == null) return; + + BlockDBCacheNode cur = Tail; + while (cur != null) { + // Unlink the nodes + cur.Prev = null; + BlockDBCacheNode next = cur.Next; + cur.Next = null; + cur = next; + } + Head = null; Tail = null; + } + } + + void AddNextNode() { + BlockDBCacheNode newHead = new BlockDBCacheNode(nextSize); + newHead.Prev = Head; + if (Head != null) Head.Next = newHead; + Head = newHead; + if (Tail == null) Tail = Head; + + // use smaller increases at first to minimise memory usage + if (nextSize == 50 * 1000) nextSize = 100 * 1000; + if (nextSize == 20 * 1000) nextSize = 50 * 1000; + if (nextSize == 10 * 1000) nextSize = 20 * 1000; + } + + int nextSize = 10 * 1000; } // TODO: track start time so we can use int16 instead of int32 time delta @@ -37,5 +76,9 @@ namespace MCGalaxy.DB { public int Count; public BlockDBEntry[] Entries; + + public BlockDBCacheNode(int capacity) { + Entries = new BlockDBEntry[capacity]; + } } } diff --git a/MCGalaxy/Database/BlockDB/BlockDBFile.cs b/MCGalaxy/Database/BlockDB/BlockDBFile.cs index 9beea397e..053eaadfd 100644 --- a/MCGalaxy/Database/BlockDB/BlockDBFile.cs +++ b/MCGalaxy/Database/BlockDB/BlockDBFile.cs @@ -61,11 +61,27 @@ namespace MCGalaxy.DB { public static void WriteEntries(Stream s, FastList entries) { byte[] bulk = new byte[BulkEntries * EntrySize]; + WriteEntries(s, bulk, entries.Items, entries.Count); + } + + public static void WriteEntries(Stream s, BlockDBCache cache) { + byte[] bulk = new byte[BulkEntries * EntrySize]; + BlockDBCacheNode node = cache.Tail; - for (int i = 0; i < entries.Count; i += BulkEntries) { - int count = Math.Min(BulkEntries, entries.Count - i); - for (int j = 0; j < count; j++) { - BlockDBEntry entry = entries.Items[i + j]; + while (node != null) { + WriteEntries(s, bulk, node.Entries, node.Count); + lock (cache.Locker) + node = node.Next; + } + } + + static void WriteEntries(Stream s, byte[] bulk, BlockDBEntry[] entries, int count) { + if (count == 0) return; + + for (int i = 0; i < count; i += BulkEntries) { + int bulkCount = Math.Min(BulkEntries, count - i); + for (int j = 0; j < bulkCount; j++) { + BlockDBEntry entry = entries[i + j]; WriteI32(entry.PlayerID, bulk, j * EntrySize); WriteI32(entry.TimeDelta, bulk, j * EntrySize + 4); WriteI32(entry.Index, bulk, j * EntrySize + 8); @@ -73,7 +89,7 @@ namespace MCGalaxy.DB { bulk[j * EntrySize + 13] = entry.NewRaw; WriteU16(entry.Flags, bulk, j * EntrySize + 14); } - s.Write(bulk, 0, count * EntrySize); + s.Write(bulk, 0, bulkCount * EntrySize); } } @@ -101,7 +117,7 @@ namespace MCGalaxy.DB { /// Iterates from the very newest to oldest entry in the BlockDB. /// whether an entry before start time was reached. - public static bool FindChangesBy(Stream s, int[] ids, long start, long end, + public static bool FindChangesBy(Stream s, int[] ids, int start, int end, Action output) { byte[] bulk = new byte[BulkEntries * EntrySize]; fixed (byte* ptr = bulk) { diff --git a/MCGalaxy/Levels/LevelDB.cs b/MCGalaxy/Levels/LevelDB.cs index a209caabc..c2295e3f3 100644 --- a/MCGalaxy/Levels/LevelDB.cs +++ b/MCGalaxy/Levels/LevelDB.cs @@ -25,7 +25,7 @@ namespace MCGalaxy { public static class LevelDB { public unsafe static void SaveBlockDB(Level lvl) { - if (lvl.BlockDB.Cache.Count == 0) return; + if (lvl.BlockDB.Cache.Head == null) return; if (!lvl.UseBlockDB) { lvl.BlockDB.Cache.Clear(); return; } lvl.BlockDB.WriteEntries(); diff --git a/MCGalaxy/MCGalaxy_.csproj b/MCGalaxy/MCGalaxy_.csproj index b4567a154..03b233270 100644 --- a/MCGalaxy/MCGalaxy_.csproj +++ b/MCGalaxy/MCGalaxy_.csproj @@ -403,6 +403,7 @@ +