DB: in-memory cache is now a doubly linked list with bulk entries

This commit is contained in:
UnknownShadow200 2017-01-08 14:44:28 +11:00
parent 13c398d923
commit 9821f81572
5 changed files with 114 additions and 42 deletions

View File

@ -36,12 +36,9 @@ namespace MCGalaxy.DB {
/// <summary> The path of this BlockDB's backing file on disc. </summary>
public string FilePath { get { return BlockDBFile.FilePath(MapName); } }
/// <summary> Used to synchronise adding to Cache by multiple threads. </summary>
internal readonly object CacheLock = new object();
/// <summary> In-memory list of recent BlockDB changes. </summary>
public FastList<BlockDBEntry> Cache = new FastList<BlockDBEntry>();
public BlockDBCache Cache = new BlockDBCache();
/// <summary> Whether changes are actually added to the BlockDB. </summary>
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<BlockDBEntry>();
Cache.Clear();
}
}
}
@ -124,13 +117,17 @@ namespace MCGalaxy.DB {
}
void FindInMemoryAt(ushort x, ushort y, ushort z, Action<BlockDBEntry> 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 {
/// <returns> whether an entry before start time was reached. </returns>
public bool FindChangesBy(int[] ids, DateTime start, DateTime end,
out Vec3U16 dims, Action<BlockDBEntry> 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<BlockDBEntry> 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;
}

View File

@ -16,17 +16,56 @@
permissions and limitations under the Licenses.
*/
using System;
using System.Data;
using MCGalaxy.SQL;
namespace MCGalaxy.DB {
/// <summary> Optimised in-memory BlockDB cache. </summary>
public sealed class BlockDBCache {
public BlockDBCacheNode Head, Tail;
public BlockDBCacheNode Tail, Head;
int nextSize = 10000;
/// <summary> Used to synchronise adding to Cache by multiple threads. </summary>
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];
}
}
}

View File

@ -61,11 +61,27 @@ namespace MCGalaxy.DB {
public static void WriteEntries(Stream s, FastList<BlockDBEntry> 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 {
/// <summary> Iterates from the very newest to oldest entry in the BlockDB. </summary>
/// <returns> whether an entry before start time was reached. </returns>
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<BlockDBEntry> output) {
byte[] bulk = new byte[BulkEntries * EntrySize];
fixed (byte* ptr = bulk) {

View File

@ -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();

View File

@ -403,6 +403,7 @@
<Compile Include="CorePlugin\LevelHandler.cs" />
<Compile Include="Database\Backends\SQLite.cs" />
<Compile Include="Database\BlockDB\BlockDB.cs" />
<Compile Include="Database\BlockDB\BlockDBCache.cs" />
<Compile Include="Database\BlockDB\BlockDBFile.cs" />
<Compile Include="Database\BlockDB\BlockDBEntry.cs" />
<Compile Include="Database\BlockDB\BlockDBChange.cs" />