[Major WIP] Rewrite UndoFile classes to be much more extensible.

This commit is contained in:
UnknownShadow200 2016-08-19 23:55:26 +10:00
parent 6b7b3cd191
commit 075e99f118
4 changed files with 112 additions and 349 deletions

View File

@ -29,17 +29,18 @@ namespace MCGalaxy.Util {
public static UndoFile BinFormat = new UndoFileBin(); public static UndoFile BinFormat = new UndoFileBin();
public static UndoFile NewFormat = new UndoFileCBin(); public static UndoFile NewFormat = new UndoFileCBin();
protected class UndoEntriesArgs {
public Player Player;
public byte[] Temp;
public bool Stop;
public DateTime StartRange;
}
protected abstract void SaveUndoData(List<Player.UndoPos> buffer, string path); protected abstract void SaveUndoData(List<Player.UndoPos> buffer, string path);
protected abstract void SaveUndoData(UndoCache buffer, string path); protected abstract void SaveUndoData(UndoCache buffer, string path);
protected abstract void ReadUndoData(List<Player.UndoPos> buffer, string path); protected abstract IEnumerable<Player.UndoPos> GetEntries(Stream s, UndoEntriesArgs args);
protected abstract bool UndoEntry(Player p, string path, Vec3S32[] marks,
ref byte[] temp, DateTime start);
protected abstract bool HighlightEntry(Player p, string path,
ref byte[] temp, DateTime start);
protected abstract string Extension { get; } protected abstract string Extension { get; }

View File

@ -19,7 +19,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using MCGalaxy.Drawing;
namespace MCGalaxy.Util { namespace MCGalaxy.Util {
@ -36,123 +35,46 @@ namespace MCGalaxy.Util {
throw new NotSupportedException("Non-optimised binary undo files have been deprecated"); throw new NotSupportedException("Non-optimised binary undo files have been deprecated");
} }
protected override void ReadUndoData(List<Player.UndoPos> buffer, string path) { protected override IEnumerable<Player.UndoPos> GetEntries(Stream s, UndoEntriesArgs args) {
Player.UndoPos Pos;
using (Stream fs = File.OpenRead(path))
using (BinaryReader r = new BinaryReader(fs))
{
int approxEntries = (int)(fs.Length / entrySize);
if (buffer.Capacity < approxEntries)
buffer.Capacity = approxEntries;
while (fs.Position < fs.Length) {
ChunkHeader chunk = ReadHeader(fs, r);
Pos.mapName = chunk.LevelName;
for (int j = 0; j < chunk.Entries; j++ ) {
DateTime rawTime = chunk.BaseTime.AddSeconds(r.ReadUInt16());
Pos.timeDelta = (int)rawTime.Subtract(Server.StartTime).TotalSeconds;
Pos.x = r.ReadUInt16(); Pos.y = r.ReadUInt16(); Pos.z = r.ReadUInt16();
Pos.type = r.ReadByte(); Pos.extType = r.ReadByte();
Pos.newtype = r.ReadByte(); Pos.newExtType = r.ReadByte();
buffer.Add(Pos);
}
}
}
}
protected override bool UndoEntry(Player p, string path, Vec3S32[] marks,
ref byte[] temp, DateTime start) {
List<ChunkHeader> list = new List<ChunkHeader>(); List<ChunkHeader> list = new List<ChunkHeader>();
int timeDelta = (int)DateTime.UtcNow.Subtract(Server.StartTime).TotalSeconds; Player.UndoPos pos = default(Player.UndoPos);
Player.UndoPos Pos = default(Player.UndoPos); bool super = args.Player == null || args.Player.ircNick != null;
Vec3U16 min = (Vec3U16)marks[0], max = (Vec3U16)marks[1]; DateTime start = args.StartRange;
bool undoArea = min.X != ushort.MaxValue;
using (Stream fs = File.OpenRead(path)) ReadHeaders(list, s);
using (BinaryReader r = new BinaryReader(fs))
{
ReadHeaders(list, r);
for (int i = list.Count - 1; i >= 0; i--) { for (int i = list.Count - 1; i >= 0; i--) {
ChunkHeader chunk = list[i]; ChunkHeader chunk = list[i];
Level lvl; // Can we safely discard the entire chunk?
if (!CheckChunk(chunk, start, p, out lvl)) return false; bool inRange = chunk.BaseTime.AddTicks(65536 * TimeSpan.TicksPerSecond) >= start;
if (lvl == null) continue; if (!inRange) { args.Stop = true; yield break; }
bool super = p == null || p.ircNick != null; if (!super && !args.Player.level.name.CaselessEq(chunk.LevelName)) continue;
if (!super && !p.level.name.CaselessEq(lvl.name)) continue;
BufferedBlockSender buffer = new BufferedBlockSender(lvl); s.Seek(chunk.DataPosition, SeekOrigin.Begin);
if (!undoArea) { if (args.Temp == null)
min = new Vec3U16(0, 0, 0); args.Temp = new byte[ushort.MaxValue * entrySize];
max = new Vec3U16((ushort)(lvl.Width - 1), (ushort)(lvl.Height - 1), (ushort)(lvl.Length - 1)); s.Read(args.Temp, 0, chunk.Entries * entrySize);
} byte[] temp = args.Temp;
Pos.mapName = chunk.LevelName;
fs.Seek(chunk.DataPosition, SeekOrigin.Begin);
if (temp == null) temp = new byte[ushort.MaxValue * entrySize];
fs.Read(temp, 0, chunk.Entries * entrySize);
for (int j = chunk.Entries - 1; j >= 0; j-- ) { for (int j = chunk.Entries - 1; j >= 0; j-- ) {
int offset = j * entrySize; int offset = j * entrySize;
DateTime time = chunk.BaseTime.AddTicks(U16(temp, offset + 0) * TimeSpan.TicksPerSecond); DateTime time = chunk.BaseTime.AddTicks(U16(temp, offset + 0) * TimeSpan.TicksPerSecond);
if (time < start) { buffer.CheckIfSend(true); return false; } if (time < start) { args.Stop = true; yield break; }
Pos.x = U16(temp, offset + 2); Pos.y = U16(temp, offset + 4); Pos.z = U16(temp, offset + 6);
if (Pos.x < min.X || Pos.y < min.Y || Pos.z < min.Z ||
Pos.x > max.X || Pos.y > max.Y || Pos.z > max.Z) continue;
Pos.type = temp[offset + 8]; Pos.extType = temp[offset + 9]; pos.x = U16(temp, offset + 2);
Pos.newtype = temp[offset + 10]; Pos.newExtType = temp[offset + 11]; pos.y = U16(temp, offset + 4);
UndoBlock(p, lvl, Pos, timeDelta, buffer); pos.z = U16(temp, offset + 6);
}
buffer.CheckIfSend(true);
}
}
return true;
}
protected override bool HighlightEntry(Player p, string path, pos.type = temp[offset + 8]; pos.extType = temp[offset + 9];
ref byte[] temp, DateTime start) { pos.newtype = temp[offset + 10]; pos.newExtType = temp[offset + 11];
List<ChunkHeader> list = new List<ChunkHeader>(); yield return pos;
using (Stream fs = File.OpenRead(path))
using (BinaryReader r = new BinaryReader(fs))
{
ReadHeaders(list, r);
for (int i = list.Count - 1; i >= 0; i--) {
ChunkHeader chunk = list[i];
Level lvl;
if (!CheckChunk(chunk, start, p, out lvl))
return false;
if (lvl == null || lvl != p.level) continue;
fs.Seek(chunk.DataPosition, SeekOrigin.Begin);
if (temp == null) temp = new byte[ushort.MaxValue * entrySize];
fs.Read(temp, 0, chunk.Entries * entrySize);
for (int j = chunk.Entries - 1; j >= 0; j-- ) {
int offset = j * entrySize;
DateTime time = chunk.BaseTime.AddTicks(U16(temp, offset + 0) * TimeSpan.TicksPerSecond);
if (time < start) return false;
ushort x = U16(temp, offset + 2), y = U16(temp, offset + 4), z = U16(temp, offset + 6);
HighlightBlock(p, lvl, temp[offset + 8], temp[offset + 10], x, y, z);
} }
} }
} }
return true;
}
static ushort U16(byte[] buffer, int offset) { static ushort U16(byte[] buffer, int offset) {
return (ushort)(buffer[offset + 0] | buffer[offset + 1] << 8); return (ushort)(buffer[offset + 0] | buffer[offset + 1] << 8);
} }
static bool CheckChunk(ChunkHeader chunk, DateTime start, Player p, out Level lvl) {
DateTime time = chunk.BaseTime;
lvl = null;
if (time.AddTicks(65536 * TimeSpan.TicksPerSecond) < start)
return false; // we can safely discard the entire chunk
lvl = LevelInfo.FindExact(chunk.LevelName);
return true;
}
struct ChunkHeader { struct ChunkHeader {
public string LevelName; public string LevelName;
public DateTime BaseTime; public DateTime BaseTime;
@ -160,8 +82,8 @@ namespace MCGalaxy.Util {
public long DataPosition; public long DataPosition;
} }
static void ReadHeaders(List<ChunkHeader> list, BinaryReader r) { static void ReadHeaders(List<ChunkHeader> list, Stream s) {
Stream s = r.BaseStream; BinaryReader r = new BinaryReader(s);
long len = s.Length; long len = s.Length;
while (s.Position < len) { while (s.Position < len) {
ChunkHeader header = ReadHeader(s, r); ChunkHeader header = ReadHeader(s, r);

View File

@ -19,7 +19,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using MCGalaxy.Drawing;
using MCGalaxy.Levels.IO; using MCGalaxy.Levels.IO;
namespace MCGalaxy.Util { namespace MCGalaxy.Util {
@ -99,129 +98,46 @@ namespace MCGalaxy.Util {
} }
} }
protected override void ReadUndoData(List<Player.UndoPos> buffer, string path) { protected override IEnumerable<Player.UndoPos> GetEntries(Stream s, UndoEntriesArgs args) {
Player.UndoPos Pos;
UndoCacheItem item = default(UndoCacheItem);
using (Stream fs = File.OpenRead(path))
using (BinaryReader r = new BinaryReader(fs))
{
int approxEntries = (int)(fs.Length / entrySize);
if (buffer.Capacity < approxEntries)
buffer.Capacity = approxEntries;
while (fs.Position < fs.Length) {
ChunkHeader chunk = ReadHeader(fs, r);
Pos.mapName = chunk.LevelName;
for (int j = 0; j < chunk.Entries; j++ ) {
item.Flags = r.ReadUInt16();
DateTime time = chunk.BaseTime.AddTicks((item.Flags & 0x3FFF) * TimeSpan.TicksPerSecond);
Pos.timeDelta = (int)time.Subtract(Server.StartTime).TotalSeconds;
int index = r.ReadInt32();
Pos.x = (ushort)(index % chunk.Width);
Pos.y = (ushort)((index / chunk.Width) / chunk.Length);
Pos.z = (ushort)((index / chunk.Width) % chunk.Length);
item.Type = r.ReadByte();
item.NewType = r.ReadByte();
item.GetBlock(out Pos.type, out Pos.extType);
item.GetNewBlock(out Pos.newtype, out Pos.newExtType);
buffer.Add(Pos);
}
}
}
}
protected override bool UndoEntry(Player p, string path, Vec3S32[] marks,
ref byte[] temp, DateTime start) {
List<ChunkHeader> list = new List<ChunkHeader>(); List<ChunkHeader> list = new List<ChunkHeader>();
int timeDelta = (int)DateTime.UtcNow.Subtract(Server.StartTime).TotalSeconds; Player.UndoPos pos = default(Player.UndoPos);
Player.UndoPos Pos = default(Player.UndoPos);
Vec3U16 min = (Vec3U16)marks[0], max = (Vec3U16)marks[1];
bool undoArea = min.X != ushort.MaxValue;
UndoCacheItem item = default(UndoCacheItem); UndoCacheItem item = default(UndoCacheItem);
bool super = args.Player == null || args.Player.ircNick != null;
DateTime start = args.StartRange;
using (Stream fs = File.OpenRead(path)) ReadHeaders(list, s);
using (BinaryReader r = new BinaryReader(fs))
{
ReadHeaders(list, r);
for (int i = list.Count - 1; i >= 0; i--) { for (int i = list.Count - 1; i >= 0; i--) {
ChunkHeader chunk = list[i]; ChunkHeader chunk = list[i];
Level lvl; // Can we safely discard the entire chunk?
if (!CheckChunk(chunk, start, p, out lvl)) return false; bool inRange = chunk.BaseTime.AddTicks((65536 >> 2) * TimeSpan.TicksPerSecond) >= start;
if (lvl == null) continue; if (!inRange) { args.Stop = true; yield break; }
bool super = p == null || p.ircNick != null; if (!super && !args.Player.level.name.CaselessEq(chunk.LevelName)) continue;
if (!super && !p.level.name.CaselessEq(lvl.name)) continue; pos.mapName = chunk.LevelName;
BufferedBlockSender buffer = new BufferedBlockSender(lvl); s.Seek(chunk.DataPosition, SeekOrigin.Begin);
if (!undoArea) { if (args.Temp == null)
min = new Vec3U16(0, 0, 0); args.Temp = new byte[ushort.MaxValue * entrySize];
max = new Vec3U16((ushort)(lvl.Width - 1), (ushort)(lvl.Height - 1), (ushort)(lvl.Length - 1)); s.Read(args.Temp, 0, chunk.Entries * entrySize);
} byte[] temp = args.Temp;
Pos.mapName = chunk.LevelName;
fs.Seek(chunk.DataPosition, SeekOrigin.Begin);
if (temp == null) temp = new byte[ushort.MaxValue * entrySize];
fs.Read(temp, 0, chunk.Entries * entrySize);
for (int j = chunk.Entries - 1; j >= 0; j-- ) { for (int j = chunk.Entries - 1; j >= 0; j-- ) {
int offset = j * entrySize; int offset = j * entrySize;
item.Flags = U16(temp, offset + 0); item.Flags = U16(temp, offset + 0);
DateTime time = chunk.BaseTime.AddTicks((item.Flags & 0x3FFF) * TimeSpan.TicksPerSecond); DateTime time = chunk.BaseTime.AddTicks((item.Flags & 0x3FFF) * TimeSpan.TicksPerSecond);
if (time < start) { buffer.CheckIfSend(true); return false; } if (time < start) { args.Stop = true; yield break; }
int index = I32(temp, offset + 2); int index = I32(temp, offset + 2);
Pos.x = (ushort)(index % chunk.Width); pos.x = (ushort)(index % chunk.Width);
Pos.y = (ushort)((index / chunk.Width) / chunk.Length); pos.y = (ushort)((index / chunk.Width) / chunk.Length);
Pos.z = (ushort)((index / chunk.Width) % chunk.Length); pos.z = (ushort)((index / chunk.Width) % chunk.Length);
if (Pos.x < min.X || Pos.y < min.Y || Pos.z < min.Z ||
Pos.x > max.X || Pos.y > max.Y || Pos.z > max.Z) continue;
item.Type = temp[offset + 6]; item.Type = temp[offset + 6];
item.NewType = temp[offset + 7]; item.NewType = temp[offset + 7];
item.GetBlock(out Pos.type, out Pos.extType); item.GetBlock(out pos.type, out pos.extType);
item.GetNewBlock(out Pos.newtype, out Pos.newExtType); item.GetNewBlock(out pos.newtype, out pos.newExtType);
UndoBlock(p, lvl, Pos, timeDelta, buffer); yield return pos;
}
buffer.CheckIfSend(true);
} }
} }
return true;
}
protected override bool HighlightEntry(Player p, string path,
ref byte[] temp, DateTime start) {
List<ChunkHeader> list = new List<ChunkHeader>();
using (Stream fs = File.OpenRead(path))
using (BinaryReader r = new BinaryReader(fs))
{
ReadHeaders(list, r);
for (int i = list.Count - 1; i >= 0; i--) {
ChunkHeader chunk = list[i];
Level lvl;
if (!CheckChunk(chunk, start, p, out lvl))
return false;
if (lvl == null || lvl != p.level) continue;
fs.Seek(chunk.DataPosition, SeekOrigin.Begin);
if (temp == null) temp = new byte[ushort.MaxValue * entrySize];
fs.Read(temp, 0, chunk.Entries * entrySize);
for (int j = chunk.Entries - 1; j >= 0; j-- ) {
int offset = j * entrySize;
ushort flags = U16(temp, offset + 0);
DateTime time = chunk.BaseTime.AddTicks((flags & 0x3FFF) * TimeSpan.TicksPerSecond);
if (time < start) return false;
int index = I32(temp, offset + 2);
ushort x = (ushort)(index % chunk.Width);
ushort y = (ushort)((index / chunk.Width) / chunk.Length);
ushort z = (ushort)((index / chunk.Width) % chunk.Length);
HighlightBlock(p, lvl, temp[offset + 6], temp[offset + 7], x, y, z);
}
}
}
return true;
} }
static ushort U16(byte[] buffer, int offset) { static ushort U16(byte[] buffer, int offset) {
@ -233,15 +149,6 @@ namespace MCGalaxy.Util {
buffer[offset + 2] << 16 | buffer[offset + 3] << 24; buffer[offset + 2] << 16 | buffer[offset + 3] << 24;
} }
static bool CheckChunk(ChunkHeader chunk, DateTime start, Player p, out Level lvl) {
DateTime time = chunk.BaseTime;
lvl = null;
if (time.AddTicks((65536 >> 2) * TimeSpan.TicksPerSecond) < start)
return false; // we can safely discard the entire chunk
lvl = LevelInfo.FindExact(chunk.LevelName);
return true;
}
struct ChunkHeader { struct ChunkHeader {
public string LevelName; public string LevelName;
public DateTime BaseTime; public DateTime BaseTime;
@ -250,8 +157,8 @@ namespace MCGalaxy.Util {
public long DataPosition; public long DataPosition;
} }
static void ReadHeaders(List<ChunkHeader> list, BinaryReader r) { static void ReadHeaders(List<ChunkHeader> list, Stream s) {
Stream s = r.BaseStream; BinaryReader r = new BinaryReader(s);
long len = s.Length; long len = s.Length;
while (s.Position < len) { while (s.Position < len) {
ChunkHeader header = ReadHeader(s, r); ChunkHeader header = ReadHeader(s, r);

View File

@ -19,7 +19,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using MCGalaxy.Drawing;
namespace MCGalaxy.Util { namespace MCGalaxy.Util {
@ -35,98 +34,32 @@ namespace MCGalaxy.Util {
throw new NotSupportedException("Text undo files have been deprecated"); throw new NotSupportedException("Text undo files have been deprecated");
} }
protected override void ReadUndoData(List<Player.UndoPos> buffer, string path) { protected override IEnumerable<Player.UndoPos> GetEntries(Stream s, UndoEntriesArgs args) {
Player.UndoPos Pos; Player.UndoPos pos = default(Player.UndoPos);
Pos.extType = 0; Pos.newExtType = 0; string[] lines = new StreamReader(s).ReadToEnd().Split(' ');
string[] lines = File.ReadAllText(path).Split(' '); Player p = args.Player;
int approxEntries = (int)(lines.Length / 7); bool super = p == null || p.ircNick != null;
if (buffer.Capacity < approxEntries) DateTime start = args.StartRange;
buffer.Capacity = approxEntries;
for (int i = 0; i < lines.Length; i += 7) {
if (lines[i].Length == 0) continue;
Pos.mapName = lines[i];
Pos.x = ushort.Parse(lines[i + 1]);
Pos.y = ushort.Parse(lines[i + 2]);
Pos.z = ushort.Parse(lines[i + 3]);
string time = lines[i + 4].Replace('&', ' ');
DateTime rawTime = DateTime.Parse(time, CultureInfo.InvariantCulture);
Pos.timeDelta = (int)rawTime.Subtract(Server.StartTimeLocal).TotalSeconds;
Pos.type = byte.Parse(lines[i + 5]);
Pos.newtype = byte.Parse(lines[i + 6]);
buffer.Add(Pos);
}
}
protected override bool UndoEntry(Player p, string path, Vec3S32[] marks,
ref byte[] temp, DateTime start) {
Player.UndoPos Pos = default(Player.UndoPos);
int timeDelta = (int)DateTime.UtcNow.Subtract(Server.StartTime).TotalSeconds;
string[] lines = File.ReadAllText(path).Split(' ');
Vec3U16 min = (Vec3U16)marks[0], max = (Vec3U16)marks[1];
bool undoArea = min.X != ushort.MaxValue;
BufferedBlockSender buffer = new BufferedBlockSender();
string last = null;
// because we have space to end of each entry, need to subtract one otherwise we'll start at a "". // because we have space to end of each entry, need to subtract one otherwise we'll start at a "".
for (int i = (lines.Length - 1) / 7; i >= 0; i--) { for (int i = (lines.Length - 1) / 7; i >= 0; i--) {
try {
// line format: mapName x y z date oldblock newblock // line format: mapName x y z date oldblock newblock
if (!InTime(lines[(i * 7) - 3], start)) return false; string timeRaw = lines[(i * 7) - 3].Replace('&', ' ');
Level lvl = LevelInfo.FindExact(lines[(i * 7) - 7]); DateTime time = DateTime.Parse(timeRaw, CultureInfo.InvariantCulture);
if (lvl == null || (p.level != null && !p.level.name.CaselessEq(lvl.name))) if (time < start) { args.Stop = true; yield break; }
continue;
if (!undoArea) {
min = new Vec3U16(0, 0, 0);
max = new Vec3U16((ushort)(lvl.Width - 1), (ushort)(lvl.Height - 1), (ushort)(lvl.Length - 1));
}
if (last == null || last != lvl.name) {
buffer.CheckIfSend(true);
last = lvl.name;
}
buffer.level = lvl;
Pos.mapName = lvl.name;
Pos.x = Convert.ToUInt16(lines[(i * 7) - 6]); string map = lines[(i * 7) - 7];
Pos.y = Convert.ToUInt16(lines[(i * 7) - 5]); if (!super && !p.level.name.CaselessEq(map)) continue;
Pos.z = Convert.ToUInt16(lines[(i * 7) - 4]); pos.mapName = map;
if (Pos.x < min.X || Pos.y < min.Y || Pos.z < min.Z ||
Pos.x > max.X || Pos.y > max.Y || Pos.z > max.Z) continue;
Pos.newtype = Convert.ToByte(lines[(i * 7) - 1]); pos.x = Convert.ToUInt16(lines[(i * 7) - 6]);
Pos.type = Convert.ToByte(lines[(i * 7) - 2]); pos.y = Convert.ToUInt16(lines[(i * 7) - 5]);
UndoBlock(p, lvl, Pos, timeDelta, buffer); pos.z = Convert.ToUInt16(lines[(i * 7) - 4]);
} catch {
}
}
buffer.CheckIfSend(true);
return true;
}
protected override bool HighlightEntry(Player p, string path, ref byte[] temp, DateTime start) { pos.newtype = Convert.ToByte(lines[(i * 7) - 1]);
string[] lines = File.ReadAllText(path).Split(' '); pos.type = Convert.ToByte(lines[(i * 7) - 2]);
// because we have space to end of each entry, need to subtract one otherwise we'll start at a "". yield return pos;
for (int i = (lines.Length - 1) / 7; i >= 0; i--) {
try {
// line format: mapName x y z date oldblock newblock
if (!InTime(lines[(i * 7) - 3], start)) return false;
Level lvl = LevelInfo.FindExact(lines[(i * 7) - 7]);
if (lvl == null || lvl != p.level) continue;
ushort x = Convert.ToUInt16(lines[(i * 7) - 6]);
ushort y = Convert.ToUInt16(lines[(i * 7) - 5]);
ushort z = Convert.ToUInt16(lines[(i * 7) - 4]);
HighlightBlock(p, lvl, Convert.ToByte(lines[(i * 7) - 2]), Convert.ToByte(lines[(i * 7) - 1]), x, y, z);
} catch { }
} }
return true;
}
static bool InTime(string line, DateTime start) {
line = line.Replace('&', ' ');
DateTime time = DateTime.Parse(line, CultureInfo.InvariantCulture);
return time >= start;
} }
} }
} }