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); }