mirror of
https://github.com/ClassiCube/MCGalaxy.git
synced 2025-09-25 06:04:46 -04:00
Core: Start work on new BlockDB format.
This commit is contained in:
parent
b4035ffe93
commit
5202750db8
127
MCGalaxy/Database/BlockDB.Utils.cs
Normal file
127
MCGalaxy/Database/BlockDB.Utils.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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);
|
||||
|
@ -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)";
|
||||
|
@ -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" />
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user