mirror of
https://github.com/ClassiCube/MCGalaxy.git
synced 2025-09-23 12:42:22 -04:00
DB: auto flush the BlockDB. breaks undo and redo
This commit is contained in:
parent
c5a593e55f
commit
9ac5afab9f
@ -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()); }
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user