Core: Start work on new BlockDB format.

This commit is contained in:
UnknownShadow200 2016-11-06 18:02:47 +11:00
parent b4035ffe93
commit 5202750db8
9 changed files with 248 additions and 81 deletions

View File

@ -0,0 +1,127 @@
/*
Copyright 2015 MCGalaxy
Dual-licensed under the Educational Community License, Version 2.0 and
the GNU General Public License, Version 3 (the "Licenses"); you may
not use this file except in compliance with the Licenses. You may
obtain a copy of the Licenses at
http://www.opensource.org/licenses/ecl2.php
http://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing,
software distributed under the Licenses are distributed on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using MCGalaxy.Util;
namespace MCGalaxy {
public unsafe partial class BlockDB {
const byte version = 1;
const int entrySize = 16;
const int bulkEntries = 256;
public ushort Width, Height, Length;
/// <summary> The map/level name associated with this BlockDB. </summary>
public string MapName;
/// <summary> The path of this BlockDB's backing file on disc. </summary>
public string FilePath { get { return "blockdefs/" + MapName + ".cbdb"; } }
/// <summary> Base point in time that all time deltas are offset from.</summary>
public static DateTime Epoch = new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Utc);
readonly ReaderWriterLockSlim locker;
bool resizeRequired;
public BlockDB(Level lvl) {
MapName = lvl.name;
Width = lvl.Width; Height = lvl.Height; Length = lvl.Length;
locker = new ReaderWriterLockSlim();
}
/// <summary> Checks if the backing file exists on disc, and if not, creates it.
/// Also recreates the backing file if dimensions on disc are less than those in memory. </summary>
public void ValidateBackingFile(Level lvl) {
using (IDisposable writeLock = locker.AccquireWriteLock()) {
if (!File.Exists(FilePath)) {
WriteHeader(this);
} else {
Vec3U16 dims;
using (Stream s = File.OpenRead(FilePath))
ReadHeader(s, out dims);
if (dims.X < Width || dims.Y < Height || dims.Z < Length) {
ResizeBackingFile();
}
}
}
}
public void ResizeBackingFile() {
Server.s.Log("Resizing BlockDB for " + MapName, true);
throw new NotImplementedException(); // TODO: resize backing file
}
static void WriteHeader(Stream s, Vec3U16 dims) {
byte[] header = new byte[entrySize];
NetUtils.WriteAscii("CBDB_MCG", header, 0);
WriteU16(version, header, 8);
WriteU16(dims.X, header, 10);
WriteU16(dims.Y, header, 12);
WriteU16(dims.Z, header, 14);
s.Write(header, 0, header.Length);
}
static void ReadHeader(Stream s, out Vec3U16 dims) {
dims = default(Vec3U16);
byte[] header = new byte[entrySize];
ReadFully(s, header, header.Length);
// Check constants are expected
// TODO: check 8 byte string identifier
ushort fileVersion = ReadU16(header, 8);
if (fileVersion != version)
throw new NotSupportedException("only version 1 is supported");
dims.X = ReadU16(header, 10);
dims.Y = ReadU16(header, 12);
dims.Z = ReadU16(header, 14);
}
static ushort ReadU16(byte[] array, int offset) {
return (ushort)(array[offset] | array[offset + 1] << 8);
}
static void WriteU16(ushort value, byte[] array, int index) {
array[index++] = (byte)(value);
array[index++] = (byte)(value >> 8);
}
static void WriteI32(int value, byte[] array, int index) {
array[index++] = (byte)(value);
array[index++] = (byte)(value >> 8);
array[index++] = (byte)(value >> 16);
array[index++] = (byte)(value >> 24);
}
static void ReadFully(Stream stream, byte[] dst, int count) {
int total = 0;
do {
int read = stream.Read(dst, total, count - total);
if (read == 0) throw new EndOfStreamException();
total += read;
} while (total < count);
}
}
}

View File

@ -19,35 +19,20 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using MCGalaxy.Util;
namespace MCGalaxy {
public unsafe class BlockDB {
public unsafe partial class BlockDB {
const byte version = 1;
const int entrySize = 16;
const int bulkEntries = 256;
public void WriteHeader(Stream stream, Level lvl) {
byte[] header = new byte[4 * entrySize];
NetUtils.WriteAscii("CBDB", header, 0);
header[4] = version;
header[5] = 0; // ref year
WriteU16(lvl.Width, header, 6);
WriteU16(lvl.Height, header, 8);
WriteU16(lvl.Length, header, 10);
NetUtils.WriteAscii("MCGalaxy", header, entrySize);
stream.Write(header, 0, header.Length);
}
public void WriteEntries(Stream stream, List<BlockDBEntry> entries) {
public void WriteEntries(Stream stream, FastList<BlockDBEntry> entries) {
byte[] bulk = new byte[bulkEntries * entrySize];
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[i + j];
BlockDBEntry entry = entries.Items[i + j];
WriteI32(entry.PlayerID, bulk, j * entrySize);
WriteI32(entry.TimeDelta, bulk, j * entrySize + 4);
WriteI32(entry.Index, bulk, j * entrySize + 8);
@ -59,66 +44,131 @@ namespace MCGalaxy {
}
}
public List<BlockDBEntry> ReadEntries(int x, int y, int z, Stream stream) {
/// <summary> Finds all block changes which affect the given coordinates. </summary>
public void FindChangesAt(ushort x, ushort y, ushort z, Action<BlockDBEntry> output) {
using (IDisposable readLock = locker.AccquireReadLock()) {
if (!File.Exists(FilePath)) return;
using (Stream s = File.OpenRead(FilePath)) {
Vec3U16 dims;
ReadHeader(s, out dims);
if (x >= dims.X || y >= dims.Y || z >= dims.Z) return;
int index = (y * dims.Z + z) * dims.X + x;
FindChangesAt(s, index, output);
}
}
}
/// <summary> Finds all block changes by the given player. </summary>
public void FindChangesBy(int id, Action<BlockDBEntry> output, out Vec3U16 dims) {
using (IDisposable readLock = locker.AccquireReadLock()) {
if (!File.Exists(FilePath)) return;
using (Stream s = File.OpenRead(FilePath)) {
ReadHeader(s, out dims);
FindChangesBy(s, index, output);
}
}
}
/// <summary> Finds all block changes by the given players. </summary>
public void FindChangesBy(int[] ids, Action<BlockDBEntry> output, out Vec3U16 dims) {
using (IDisposable readLock = locker.AccquireReadLock()) {
if (!File.Exists(FilePath)) return;
using (Stream s = File.OpenRead(FilePath)) {
ReadHeader(s, out dims);
FindChangesBy(s, index, output);
}
}
}
// NOTE: These are duplicated since it is important to be as performant as possible,
// since the BlockDB can have millions and millions of entries
static void FindChangesAt(Stream s, int index, Action<BlockDBEntry> output) {
byte[] bulk = new byte[bulkEntries * entrySize];
ReadFully(stream, bulk, 4 * entrySize);
List<BlockDBEntry> matches = new List<BlockDBEntry>();
ushort width = ReadU16(bulk, 6);
ushort height = ReadU16(bulk, 8);
ushort length = ReadU16(bulk, 10);
int index = x + width * (z + y * length);
fixed (byte* ptr = bulk) {
int entries = (int)(stream.Length / entrySize);
int entries = (int)(s.Length / entrySize) - 1;
while (entries > 0) {
int read = Math.Min(entries, bulkEntries);
ReadFully(stream, bulk, read * entrySize);
ReadFully(s, bulk, read * entrySize);
BlockDBEntry* entryPtr = (BlockDBEntry*)ptr;
for (int i = 0; i < read; i++) {
if (entryPtr->Index != index) continue;
matches.Add(*entryPtr);
if (entryPtr->Index == index) {
output(*entryPtr);
}
entryPtr++;
}
entries -= read;
}
}
return matches;
}
static ushort ReadU16(byte[] array, int offset) {
return (ushort)(array[offset] | array[offset + 1] << 8);
static void FindChangesBy(Stream s, int id, Action<BlockDBEntry> output) {
byte[] bulk = new byte[bulkEntries * entrySize];
fixed (byte* ptr = bulk) {
int entries = (int)(s.Length / entrySize) - 1;
while (entries > 0) {
int read = Math.Min(entries, bulkEntries);
ReadFully(s, bulk, read * entrySize);
BlockDBEntry* entryPtr = (BlockDBEntry*)ptr;
for (int i = 0; i < read; i++) {
if (entryPtr->PlayerID == id) {
output(*entryPtr);
}
entryPtr++;
}
entries -= read;
}
}
}
static void WriteU16(ushort value, byte[] array, int index) {
array[index++] = (byte)(value);
array[index++] = (byte)(value >> 8);
}
static void WriteI32(int value, byte[] array, int index) {
array[index++] = (byte)(value);
array[index++] = (byte)(value >> 8);
array[index++] = (byte)(value >> 16);
array[index++] = (byte)(value >> 24);
}
static void ReadFully(Stream stream, byte[] dst, int count) {
int total = 0;
do {
int read = stream.Read(dst, total, count - total);
if (read == 0) throw new EndOfStreamException();
total += read;
} while (total < count);
static void FindChangesBy(Stream s, int[] ids, Action<BlockDBEntry> output) {
byte[] bulk = new byte[bulkEntries * entrySize];
fixed (byte* ptr = bulk) {
int entries = (int)(s.Length / entrySize) - 1;
while (entries > 0) {
int read = Math.Min(entries, bulkEntries);
ReadFully(s, bulk, read * entrySize);
BlockDBEntry* entryPtr = (BlockDBEntry*)ptr;
for (int i = 0; i < read; i++) {
for (int j = 0; j < ids.Length; j++) {
if (entryPtr->PlayerID == ids[j]) {
output(*entryPtr); break;
}
}
entryPtr++;
}
entries -= read;
}
}
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BlockDBEntry {
/// <summary> ID within Players table of player who made the change. </summary>
public int PlayerID;
/// <summary> Seconds since BlockDB.Epoch that this change occured at. </summary>
public int TimeDelta;
/// <summary> Packed coordinates of where the change occured at. </summary>
public int Index;
public byte Old, New;
/// <summary> Raw block that was previously there before the change. </summary>
public byte OldRaw;
/// <summary> Raw block that is now there due to the change. </summary>
public byte NewRaw;
/// <summary> Flags for the block change. </summary>
public ushort Flags;
}
}

View File

@ -77,9 +77,10 @@ namespace MCGalaxy {
public List<Zone> ZoneList;
public bool backedup;
internal readonly object blockCacheLock = new object();
public List<BlockPos> blockCache = new List<BlockPos>();
public FastList<BlockDBEntry> blockCache = new FastList<BlockDBEntry>();
[ConfigBool("UseBlockDB", "Other", null, true)]
public bool UseBlockDB = true;
public BlockDB BlockDB;
public byte jailrotx, jailroty;
[ConfigInt("JailX", "Jail", null, 0, 0, 65535)]

View File

@ -61,6 +61,7 @@ namespace MCGalaxy {
if (Width < 16) Width = 16;
if (Height < 16) Height = 16;
if (Length < 16) Length = 16;
BlockDB = new BlockDB(this);
VisitAccess = new LevelAccess(this, true);
BuildAccess = new LevelAccess(this, false);

View File

@ -27,7 +27,7 @@ namespace MCGalaxy {
public unsafe static void SaveBlockDB(Level lvl) {
if (lvl.blockCache.Count == 0) return;
if (!lvl.UseBlockDB) { lvl.blockCache.Clear(); return; }
List<Level.BlockPos> tempCache = lvl.blockCache;
FastList<BlockDBEntry> tempCache = lvl.blockCache;
string date = new String('-', 19); //yyyy-mm-dd hh:mm:ss
fixed (char* ptr = date) {
@ -36,11 +36,11 @@ namespace MCGalaxy {
DoSaveChanges(tempCache, ptr, lvl, date, bulk);
}
tempCache.Clear();
lvl.blockCache = new List<Level.BlockPos>();
lvl.blockCache = new FastList<BlockDBEntry>();
Server.s.Log("Saved BlockDB changes for:" + lvl.name, true);
}
unsafe static bool DoSaveChanges(List<Level.BlockPos> tempCache, char* ptr,
unsafe static bool DoSaveChanges(List<BlockDBEntry> tempCache, char* ptr,
Level lvl, string date, BulkTransaction transaction) {
string template = "INSERT INTO `Block" + lvl.name +
"` (Username, TimePerformed, X, Y, Z, type, deleted) VALUES (@0, @1, @2, @3, @4, @5, @6)";

View File

@ -407,6 +407,7 @@
<Compile Include="CorePlugin\ConnectHandler.cs" />
<Compile Include="Database\Backends\SQLite.cs" />
<Compile Include="Database\BlockDB.cs" />
<Compile Include="Database\BlockDB.Utils.cs" />
<Compile Include="Database\ColumnDesc.cs" />
<Compile Include="Database\IDatabaseBackend.cs" />
<Compile Include="Database\Backends\MySQL.cs" />

View File

@ -54,7 +54,6 @@ namespace MCGalaxy {
} else {
int index = Server.invalidIds.AddOrReplace(p.name);
p.UserID = int.MaxValue - index;
Server.s.Log("INVALID!! " + p.UserID + " - " + p.name);
}
}
}

View File

@ -126,7 +126,7 @@ namespace MCGalaxy {
1, TimeSpan.FromSeconds(Server.backupInterval));
Background.QueueRepeat(ServerTasks.BlockUpdates,
null, TimeSpan.FromSeconds(Server.blockInterval));
Background.QueueRepeat(ThreadSafeCache.CleanupTask,
Background.QueueRepeat(ThreadSafeCache.DBCache.CleanupTask,
null, TimeSpan.FromMinutes(5));
}

View File

@ -20,27 +20,22 @@ using System.Collections.Generic;
namespace MCGalaxy.Util {
public sealed class ThreadSafeCache {
static readonly List<ThreadSafeCache> caches = new List<ThreadSafeCache>();
static readonly object cachesLock = new object();
public static ThreadSafeCache DBCache = new ThreadSafeCache(() => new object());
public static ThreadSafeCache DBCache = new ThreadSafeCache(key => new object());
readonly object locker = new object();
readonly Dictionary<string, object> items = new Dictionary<string, object>();
readonly Dictionary<string, DateTime> access = new Dictionary<string, DateTime>();
readonly Func<object> constructor;
readonly Func<string, object> constructor;
public ThreadSafeCache(Func<object> constructor) {
public ThreadSafeCache(Func<string, object> constructor) {
this.constructor = constructor;
lock (cachesLock)
caches.Add(this);
}
public object Get(string key) {
lock (locker) {
object value;
if (!items.TryGetValue(key, out value)) {
value = constructor();
value = constructor(key);
items[key] = value;
}
@ -50,14 +45,7 @@ namespace MCGalaxy.Util {
}
internal static void CleanupTask(SchedulerTask task) {
lock (cachesLock) {
foreach (ThreadSafeCache cache in caches)
cache.CleanupOld();
}
}
void CleanupOld() {
public void CleanupTask(SchedulerTask task) {
List<string> free = null;
DateTime now = DateTime.UtcNow;