diff --git a/MCGalaxy/Commands/Information/CmdAbout.cs b/MCGalaxy/Commands/Information/CmdAbout.cs
index 9d712237a..c9be12b8b 100644
--- a/MCGalaxy/Commands/Information/CmdAbout.cs
+++ b/MCGalaxy/Commands/Information/CmdAbout.cs
@@ -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()); }
diff --git a/MCGalaxy/Commands/Moderation/CmdHighlight.cs b/MCGalaxy/Commands/Moderation/CmdHighlight.cs
index 76f6691c9..b5dd31436 100644
--- a/MCGalaxy/Commands/Moderation/CmdHighlight.cs
+++ b/MCGalaxy/Commands/Moderation/CmdHighlight.cs
@@ -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;
diff --git a/MCGalaxy/Database/BlockDB/BlockDB.cs b/MCGalaxy/Database/BlockDB/BlockDB.cs
index 35fa6c3b2..d66da561a 100644
--- a/MCGalaxy/Database/BlockDB/BlockDB.cs
+++ b/MCGalaxy/Database/BlockDB/BlockDB.cs
@@ -39,11 +39,13 @@ namespace MCGalaxy.DB {
/// In-memory list of recent BlockDB changes.
public BlockDBCache Cache = new BlockDBCache();
- readonly IReaderWriterLock locker;
+ /// Used to synchronise access to the in-memory and on-disc BlockDB entries.
+ 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);
}
-
+
+ /// Writes the entries from the in-memory cache to disc.
+ /// You must lock using Locker.AccquireWrite() **before** entering this method.
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();
}
}
/// Outputs all block changes which affect the given coordinates.
+ /// You must lock using Locker.AccquireRead() **before** entering this method.
public void FindChangesAt(ushort x, ushort y, ushort z, Action 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 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 {
}
/// Outputs all block changes by the given players.
+ /// You must lock using Locker.AccquireRead() **before** entering this method.
/// whether an entry before start time was reached.
public bool FindChangesBy(int[] ids, DateTime start, DateTime end,
out Vec3U16 dims, Action 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 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 {
/// Deletes the backing file on disc if it exists.
public void DeleteBackingFile() {
- using (IDisposable writeLock = locker.AccquireWrite()) {
+ using (IDisposable writeLock = Locker.AccquireWrite()) {
if (!File.Exists(FilePath)) return;
File.Delete(FilePath);
}
diff --git a/MCGalaxy/Database/BlockDB/BlockDBCache.cs b/MCGalaxy/Database/BlockDB/BlockDBCache.cs
index 3fda6ea92..49bb751e2 100644
--- a/MCGalaxy/Database/BlockDB/BlockDBCache.cs
+++ b/MCGalaxy/Database/BlockDB/BlockDBCache.cs
@@ -29,6 +29,9 @@ namespace MCGalaxy.DB {
/// Whether changes are actually added to the BlockDB.
public bool Enabled;
+
+ /// Total number of entries in the in-memory BlockDB cache.
+ public int Count;
/// Dimensions used to pack coordinates into an index.
/// May be different from actual level's dimensions, such as when the level has been resized.
@@ -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) {
diff --git a/MCGalaxy/Drawing/DrawOps/DrawOp.Performer.cs b/MCGalaxy/Drawing/DrawOps/DrawOp.Performer.cs
index 779076368..c575e919b 100644
--- a/MCGalaxy/Drawing/DrawOps/DrawOp.Performer.cs
+++ b/MCGalaxy/Drawing/DrawOps/DrawOp.Performer.cs
@@ -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();
}
}
}
diff --git a/MCGalaxy/Drawing/DrawOps/DrawOp.cs b/MCGalaxy/Drawing/DrawOps/DrawOp.cs
index 13f4105c9..1386602dd 100644
--- a/MCGalaxy/Drawing/DrawOps/DrawOp.cs
+++ b/MCGalaxy/Drawing/DrawOps/DrawOp.cs
@@ -67,6 +67,10 @@ namespace MCGalaxy.Drawing.Ops {
/// BlockDB change flags for blocks affected by this draw operation.
public ushort Flags = BlockDBFlags.Drawn;
+ /// Lock held on the associated level's BlockDB. Can be null.
+ public IDisposable BlockDBReadLock;
+
+
/// Human friendly name of the draw operation.
public abstract string Name { get; }
diff --git a/MCGalaxy/Drawing/DrawOps/RedoDrawOp.cs b/MCGalaxy/Drawing/DrawOps/RedoDrawOp.cs
index 4cb1d73b9..5d379482a 100644
--- a/MCGalaxy/Drawing/DrawOps/RedoDrawOp.cs
+++ b/MCGalaxy/Drawing/DrawOps/RedoDrawOp.cs
@@ -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;
}
diff --git a/MCGalaxy/Drawing/DrawOps/UndoDrawOp.cs b/MCGalaxy/Drawing/DrawOps/UndoDrawOp.cs
index 52d360707..cc59a37af 100644
--- a/MCGalaxy/Drawing/DrawOps/UndoDrawOp.cs
+++ b/MCGalaxy/Drawing/DrawOps/UndoDrawOp.cs
@@ -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);
diff --git a/MCGalaxy/Levels/LevelDB.cs b/MCGalaxy/Levels/LevelDB.cs
index c2295e3f3..6ee25572a 100644
--- a/MCGalaxy/Levels/LevelDB.cs
+++ b/MCGalaxy/Levels/LevelDB.cs
@@ -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);
}