mirror of
https://github.com/ClassiCube/MCGalaxy.git
synced 2025-09-24 21:51:19 -04:00
DB: in-memory cache is now a doubly linked list with bulk entries
This commit is contained in:
parent
13c398d923
commit
9821f81572
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user