DB: auto flush the BlockDB. breaks undo and redo

This commit is contained in:
UnknownShadow200 2017-01-17 08:59:20 +11:00
parent c5a593e55f
commit 9ac5afab9f
9 changed files with 93 additions and 49 deletions

View File

@ -54,8 +54,10 @@ namespace MCGalaxy.Commands {
bool foundAny = false;
ListFromDatabase(p, ref foundAny, names, x, y, z);
p.level.BlockDB.FindChangesAt(x, y, z,
entry => OutputEntry(p, ref foundAny, names, entry));
using (IDisposable rLock = p.level.BlockDB.Locker.AccquireRead()) {
p.level.BlockDB.FindChangesAt(x, y, z,
entry => OutputEntry(p, ref foundAny, names, entry));
}
if (!foundAny) Player.Message(p, "No block change records found for this block.");
BlockDBChange.OutputMessageBlock(p, b, id, x, y, z);
@ -82,7 +84,7 @@ namespace MCGalaxy.Commands {
TimeSpan delta = time - BlockDB.Epoch;
entry.TimeDelta = (int)delta.TotalSeconds;
entry.Flags = BlockDBFlags.ManualPlace;
byte flags = ParseFlags(row["Deleted"].ToString());
if ((flags & 1) == 0) { // block was placed
entry.NewRaw = byte.Parse(row["Type"].ToString());
@ -109,7 +111,7 @@ namespace MCGalaxy.Commands {
}
foundAny = true;
BlockDBChange.Output(p, name, entry);
}
}
static ushort U16(object x) { return ushort.Parse(x.ToString()); }

View File

@ -74,7 +74,7 @@ namespace MCGalaxy.Commands {
static bool ParseTimespan(string input, out TimeSpan delta) {
delta = TimeSpan.Zero;
try { delta = input.ParseShort('s'); return true;
} catch (ArgumentException) { return false;
} catch (ArgumentException) { return false;
} catch (FormatException) { return false;
}
}
@ -88,8 +88,12 @@ namespace MCGalaxy.Commands {
public bool DoHighlight(int[] ids, DateTime start, Player p) {
buffer = new BufferedBlockSender(p);
this.p = p;
bool reachedStart = p.level.BlockDB.FindChangesBy(ids, start, DateTime.MaxValue,
out dims, HighlightBlock);
bool reachedStart = false;
using (IDisposable rLock = p.level.BlockDB.Locker.AccquireRead()) {
reachedStart = p.level.BlockDB.FindChangesBy(ids, start, DateTime.MaxValue,
out dims, HighlightBlock);
}
buffer.Send(true);
buffer.player = null;
@ -102,7 +106,7 @@ namespace MCGalaxy.Commands {
void HighlightBlock(BlockDBEntry entry) {
byte oldBlock = entry.OldRaw, newBlock = entry.NewRaw;
if ((entry.Flags & BlockDBFlags.OldCustom) != 0) oldBlock = Block.custom_block;
if ((entry.Flags & BlockDBFlags.NewCustom) != 0) newBlock = Block.custom_block;
if ((entry.Flags & BlockDBFlags.NewCustom) != 0) newBlock = Block.custom_block;
if (oldBlock == Block.Invalid) return; // Exported BlockDB SQL table entries don't have previous block
found = true;

View File

@ -39,11 +39,13 @@ namespace MCGalaxy.DB {
/// <summary> In-memory list of recent BlockDB changes. </summary>
public BlockDBCache Cache = new BlockDBCache();
readonly IReaderWriterLock locker;
/// <summary> Used to synchronise access to the in-memory and on-disc BlockDB entries. </summary>
public readonly IReaderWriterLock Locker;
public BlockDB(Level lvl) {
MapName = lvl.name;
ReadDimensions();
locker = new IReaderWriterLock();
Locker = new IReaderWriterLock();
if (Dims.X < lvl.Width) Dims.X = lvl.Width;
if (Dims.Y < lvl.Height) Dims.Y = lvl.Height;
@ -56,43 +58,42 @@ namespace MCGalaxy.DB {
using (Stream s = File.OpenRead(FilePath))
BlockDBFile.ReadHeader(s, out Dims);
}
/// <summary> Writes the entries from the in-memory cache to disc. </summary>
/// <remarks> You must lock using Locker.AccquireWrite() **before** entering this method. </remarks>
public void WriteEntries() {
using (IDisposable writeLock = locker.AccquireWrite()) {
if (Cache.Head == null) return;
ValidateBackingFile();
using (Stream s = File.OpenWrite(FilePath)) {
// This truncates the lower 4 bits off - so e.g. if a power off occurred
// and 21 bytes were in the file, this sets the position to byte 16
s.Position = s.Length & ~0x0F;
BlockDBFile.WriteEntries(s, Cache);
Cache.Clear();
}
if (Cache.Head == null) return;
ValidateBackingFile();
using (Stream s = File.OpenWrite(FilePath)) {
// This truncates the lower 4 bits off - so e.g. if a power off occurred
// and 21 bytes were in the file, this sets the position to byte 16
s.Position = s.Length & ~0x0F;
BlockDBFile.WriteEntries(s, Cache);
Cache.Clear();
}
}
/// <summary> Outputs all block changes which affect the given coordinates. </summary>
/// <remarks> You must lock using Locker.AccquireRead() **before** entering this method. </remarks>
public void FindChangesAt(ushort x, ushort y, ushort z, Action<BlockDBEntry> output) {
using (IDisposable readLock = locker.AccquireRead()) {
if (!File.Exists(FilePath)) { FindInMemoryAt(x, y, z, output); return; }
Vec3U16 dims;
if (!File.Exists(FilePath)) { FindInMemoryAt(x, y, z, output); return; }
Vec3U16 dims;
using (Stream s = File.OpenRead(FilePath)) {
BlockDBFile.ReadHeader(s, out dims);
if (x >= dims.X || y >= dims.Y || z >= dims.Z) return;
using (Stream s = File.OpenRead(FilePath)) {
BlockDBFile.ReadHeader(s, out dims);
if (x >= dims.X || y >= dims.Y || z >= dims.Z) return;
int index = (y * dims.Z + z) * dims.X + x;
BlockDBFile.FindChangesAt(s, index, output);
}
FindInMemoryAt(x, y, z, output);
int index = (y * dims.Z + z) * dims.X + x;
BlockDBFile.FindChangesAt(s, index, output);
}
FindInMemoryAt(x, y, z, output);
}
void FindInMemoryAt(ushort x, ushort y, ushort z, Action<BlockDBEntry> output) {
int index = (y * Dims.Z + z) * Dims.X + x;
BlockDBCacheNode node = Cache.Tail;
BlockDBCacheNode node = Cache.Tail;
while (node != null) {
BlockDBCacheEntry[] entries = node.Entries;
int count = node.Count;
@ -107,26 +108,25 @@ namespace MCGalaxy.DB {
}
/// <summary> Outputs all block changes by the given players. </summary>
/// <remarks> You must lock using Locker.AccquireRead() **before** entering this method. </remarks>
/// <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) {
int startDelta = ClampDelta(start.Subtract(Epoch));
int endDelta = ClampDelta(end.Subtract(Epoch));
using (IDisposable readLock = locker.AccquireRead()) {
dims = Dims;
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);
}
dims = Dims;
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;
BlockDBCacheNode node = Cache.Head;
while (node != null) {
int count = node.Count;
BlockDBCacheEntry[] entries = node.Entries;
@ -156,7 +156,7 @@ namespace MCGalaxy.DB {
/// <summary> Deletes the backing file on disc if it exists. </summary>
public void DeleteBackingFile() {
using (IDisposable writeLock = locker.AccquireWrite()) {
using (IDisposable writeLock = Locker.AccquireWrite()) {
if (!File.Exists(FilePath)) return;
File.Delete(FilePath);
}

View File

@ -29,6 +29,9 @@ namespace MCGalaxy.DB {
/// <summary> Whether changes are actually added to the BlockDB. </summary>
public bool Enabled;
/// <summary> Total number of entries in the in-memory BlockDB cache. </summary>
public int Count;
/// <summary> Dimensions used to pack coordinates into an index. </summary>
/// <remarks> May be different from actual level's dimensions, such as when the level has been resized. </remarks>
@ -65,13 +68,16 @@ namespace MCGalaxy.DB {
int delta = Math.Abs(timeDelta - Head.BaseTimeDelta);
entry.Packed1 |= (uint)(delta & 0xFF) << 24;
entry.Flags |= (ushort)((delta & 0x700) << 3);
Head.Entries[Head.Count] = entry; Head.Count++;
Head.Entries[Head.Count] = entry;
Head.Count++; Count++;
}
}
public void Clear() {
lock (Locker) {
if (Tail == null) return;
Count = 0;
BlockDBCacheNode cur = Tail;
while (cur != null) {

View File

@ -195,6 +195,7 @@ namespace MCGalaxy.Drawing.Ops {
p.IncrementBlockStats(b.Block, true);
// Potentially buffer the block change
if (op.TotalModified == Server.DrawReloadLimit) {
Player.Message(p, "Affected over {0} blocks, preparing to reload map..", Server.DrawReloadLimit);
lock (lvl.queueLock)
@ -205,6 +206,27 @@ namespace MCGalaxy.Drawing.Ops {
if (!same) BlockQueue.Addblock(p, index, b.Block, b.ExtBlock);
}
op.TotalModified++;
// Attempt to prevent the BlockDB from growing too large (> 1,000,000 entries)
int count = lvl.BlockDB.Cache.Count;
if (count == 0 || (count % 1000000) != 0) return;
Server.s.Log("okay.. we should probably save here"); // TODO: remove this debugging stuff
// if drawop has a read lock on BlockDB (e.g. undo/redo), we must release it here
bool hasReadLock = false;
if (op.BlockDBReadLock != null) {
op.BlockDBReadLock.Dispose();
hasReadLock = true;
}
using (IDisposable wLock = lvl.BlockDB.Locker.AccquireWrite(100)) {
Server.s.Log("GOT IT? " + (wLock == null ? "NO" : "YES"));
if (wLock != null) lvl.BlockDB.WriteEntries();
}
if (!hasReadLock) return;
op.BlockDBReadLock = lvl.BlockDB.Locker.AccquireRead();
}
}
}

View File

@ -67,6 +67,10 @@ namespace MCGalaxy.Drawing.Ops {
/// <summary> BlockDB change flags for blocks affected by this draw operation. </summary>
public ushort Flags = BlockDBFlags.Drawn;
/// <summary> Lock held on the associated level's BlockDB. Can be null. </summary>
public IDisposable BlockDBReadLock;
/// <summary> Human friendly name of the draw operation. </summary>
public abstract string Name { get; }

View File

@ -43,7 +43,9 @@ namespace MCGalaxy.Drawing.Ops {
if (ids.Length == 0) return;
this.output = output;
Level.BlockDB.FindChangesBy(ids, Start, End, out dims, RedoBlock);
using (BlockDBReadLock = Level.BlockDB.Locker.AccquireRead()) {
Level.BlockDB.FindChangesBy(ids, Start, End, out dims, RedoBlock);
}
this.output = null;
}

View File

@ -60,7 +60,9 @@ namespace MCGalaxy.Drawing.Ops {
void PerformUndo() {
int[] ids = NameConverter.FindIds(who);
if (ids.Length > 0) {
if (Level.BlockDB.FindChangesBy(ids, Start, End, out dims, UndoBlock)) return;
using (BlockDBReadLock = Level.BlockDB.Locker.AccquireRead()) {
if (Level.BlockDB.FindChangesBy(ids, Start, End, out dims, UndoBlock)) return;
}
}
UndoFormatArgs args = new UndoFormatArgs(Player, Start, End, output);

View File

@ -28,7 +28,9 @@ namespace MCGalaxy {
if (lvl.BlockDB.Cache.Head == null) return;
if (!lvl.UseBlockDB) { lvl.BlockDB.Cache.Clear(); return; }
lvl.BlockDB.WriteEntries();
using (IDisposable wLock = lvl.BlockDB.Locker.AccquireWrite()) {
lvl.BlockDB.WriteEntries();
}
Server.s.Log("Saved BlockDB changes for:" + lvl.name, true);
}