RegionFile API additions to support extensions of varying sector size

This commit is contained in:
Justin Aquadro 2012-09-02 23:39:31 -04:00
parent 613fc34df7
commit aed388dd80
2 changed files with 141 additions and 97 deletions

View file

@ -30,8 +30,8 @@ using System.Runtime.InteropServices;
// Build Number // Build Number
// Revision // Revision
// //
[assembly: AssemblyVersion("1.3.4.0")] [assembly: AssemblyVersion("1.3.5.0")]
[assembly: AssemblyFileVersion("1.3.4.0")] [assembly: AssemblyFileVersion("1.3.5.0")]
// This library is compatible with all CLS-compliant .NET programming languages. // This library is compatible with all CLS-compliant .NET programming languages.
[assembly: CLSCompliant(true)] [assembly: CLSCompliant(true)]

View file

@ -6,7 +6,8 @@ using Ionic.Zlib;
namespace Substrate.Core namespace Substrate.Core
{ {
public class RegionFile : IDisposable { public class RegionFile : IDisposable
{
private const int VERSION_GZIP = 1; private const int VERSION_GZIP = 1;
private const int VERSION_DEFLATE = 2; private const int VERSION_DEFLATE = 2;
@ -28,9 +29,10 @@ namespace Substrate.Core
private bool _disposed = false; private bool _disposed = false;
public RegionFile(string path) { public RegionFile (string path)
offsets = new int[SECTOR_INTS]; {
chunkTimestamps = new int[SECTOR_INTS]; offsets = new int[SectorInts];
chunkTimestamps = new int[SectorInts];
fileName = path; fileName = path;
Debugln("REGION LOAD " + fileName); Debugln("REGION LOAD " + fileName);
@ -94,71 +96,71 @@ namespace Substrate.Core
file = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); file = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
//using (file) { //using (file) {
if (file.Length < SECTOR_BYTES) { if (file.Length < SectorBytes) {
byte[] int0 = BitConverter.GetBytes((int)0); byte[] int0 = BitConverter.GetBytes((int)0);
/* we need to write the chunk offset table */ /* we need to write the chunk offset table */
for (int i = 0; i < SECTOR_INTS; ++i) { for (int i = 0; i < SectorInts; ++i) {
file.Write(int0, 0, 4); file.Write(int0, 0, 4);
} }
// write another sector for the timestamp info // write another sector for the timestamp info
for (int i = 0; i < SECTOR_INTS; ++i) { for (int i = 0; i < SectorInts; ++i) {
file.Write(int0, 0, 4); file.Write(int0, 0, 4);
}
file.Flush();
sizeDelta += SECTOR_BYTES * 2;
} }
if ((file.Length & 0xfff) != 0) { file.Flush();
/* the file size is not a multiple of 4KB, grow it */
for (int i = 0; i < (file.Length & 0xfff); ++i) {
file.WriteByte(0);
}
file.Flush(); sizeDelta += SectorBytes * 2;
}
if ((file.Length & 0xfff) != 0) {
/* the file size is not a multiple of 4KB, grow it */
for (int i = 0; i < (file.Length & 0xfff); ++i) {
file.WriteByte(0);
} }
/* set up the available sector map */ file.Flush();
int nSectors = (int)file.Length / SECTOR_BYTES; }
sectorFree = new List<Boolean>(nSectors);
for (int i = 0; i < nSectors; ++i) { /* set up the available sector map */
sectorFree.Add(true); int nSectors = (int)file.Length / SectorBytes;
sectorFree = new List<Boolean>(nSectors);
for (int i = 0; i < nSectors; ++i) {
sectorFree.Add(true);
}
sectorFree[0] = false; // chunk offset table
sectorFree[1] = false; // for the last modified info
file.Seek(0, SeekOrigin.Begin);
for (int i = 0; i < SectorInts; ++i) {
byte[] offsetBytes = new byte[4];
file.Read(offsetBytes, 0, 4);
if (BitConverter.IsLittleEndian) {
Array.Reverse(offsetBytes);
} }
int offset = BitConverter.ToInt32(offsetBytes, 0);
sectorFree[0] = false; // chunk offset table offsets[i] = offset;
sectorFree[1] = false; // for the last modified info if (offset != 0 && (offset >> 8) + (offset & 0xFF) <= sectorFree.Count) {
for (int sectorNum = 0; sectorNum < (offset & 0xFF); ++sectorNum) {
file.Seek(0, SeekOrigin.Begin); sectorFree[(offset >> 8) + sectorNum] = false;
for (int i = 0; i < SECTOR_INTS; ++i) {
byte[] offsetBytes = new byte[4];
file.Read(offsetBytes, 0, 4);
if (BitConverter.IsLittleEndian) {
Array.Reverse(offsetBytes);
}
int offset = BitConverter.ToInt32(offsetBytes, 0);
offsets[i] = offset;
if (offset != 0 && (offset >> 8) + (offset & 0xFF) <= sectorFree.Count) {
for (int sectorNum = 0; sectorNum < (offset & 0xFF); ++sectorNum) {
sectorFree[(offset >> 8) + sectorNum] = false;
}
} }
} }
for (int i = 0; i < SECTOR_INTS; ++i) { }
byte[] modBytes = new byte[4]; for (int i = 0; i < SectorInts; ++i) {
file.Read(modBytes, 0, 4); byte[] modBytes = new byte[4];
file.Read(modBytes, 0, 4);
if (BitConverter.IsLittleEndian) { if (BitConverter.IsLittleEndian) {
Array.Reverse(modBytes); Array.Reverse(modBytes);
}
int lastModValue = BitConverter.ToInt32(modBytes, 0);
chunkTimestamps[i] = lastModValue;
} }
int lastModValue = BitConverter.ToInt32(modBytes, 0);
chunkTimestamps[i] = lastModValue;
}
//} //}
} }
catch (IOException e) { catch (IOException e) {
@ -168,35 +170,42 @@ namespace Substrate.Core
} }
/* the modification date of the region file when it was first opened */ /* the modification date of the region file when it was first opened */
public long LastModified() { public long LastModified ()
{
return lastModified; return lastModified;
} }
/* gets how much the region file has grown since it was last checked */ /* gets how much the region file has grown since it was last checked */
public int GetSizeDelta() { public int GetSizeDelta ()
{
int ret = sizeDelta; int ret = sizeDelta;
sizeDelta = 0; sizeDelta = 0;
return ret; return ret;
} }
// various small debug printing helpers // various small debug printing helpers
private void Debug(String str) { private void Debug (String str)
// System.Consle.Write(str); {
// System.Consle.Write(str);
} }
private void Debugln(String str) { private void Debugln (String str)
{
Debug(str + "\n"); Debug(str + "\n");
} }
private void Debug(String mode, int x, int z, String str) { private void Debug (String mode, int x, int z, String str)
{
Debug("REGION " + mode + " " + fileName + "[" + x + "," + z + "] = " + str); Debug("REGION " + mode + " " + fileName + "[" + x + "," + z + "] = " + str);
} }
private void Debug(String mode, int x, int z, int count, String str) { private void Debug (String mode, int x, int z, int count, String str)
{
Debug("REGION " + mode + " " + fileName + "[" + x + "," + z + "] " + count + "B = " + str); Debug("REGION " + mode + " " + fileName + "[" + x + "," + z + "] " + count + "B = " + str);
} }
private void Debugln(String mode, int x, int z, String str) { private void Debugln (String mode, int x, int z, String str)
{
Debug(mode, x, z, str + "\n"); Debug(mode, x, z, str + "\n");
} }
@ -204,7 +213,8 @@ namespace Substrate.Core
* gets an (uncompressed) stream representing the chunk data returns null if * gets an (uncompressed) stream representing the chunk data returns null if
* the chunk is not found or an error occurs * the chunk is not found or an error occurs
*/ */
public Stream GetChunkDataInputStream(int x, int z) { public Stream GetChunkDataInputStream (int x, int z)
{
if (_disposed) { if (_disposed) {
throw new ObjectDisposedException("RegionFile", "Attempting to use a RegionFile after it has been disposed."); throw new ObjectDisposedException("RegionFile", "Attempting to use a RegionFile after it has been disposed.");
} }
@ -229,7 +239,7 @@ namespace Substrate.Core
return null; return null;
} }
file.Seek(sectorNumber * SECTOR_BYTES, SeekOrigin.Begin); file.Seek(sectorNumber * SectorBytes, SeekOrigin.Begin);
byte[] lengthBytes = new byte[4]; byte[] lengthBytes = new byte[4];
file.Read(lengthBytes, 0, 4); file.Read(lengthBytes, 0, 4);
@ -238,7 +248,7 @@ namespace Substrate.Core
} }
int length = BitConverter.ToInt32(lengthBytes, 0); int length = BitConverter.ToInt32(lengthBytes, 0);
if (length > SECTOR_BYTES * numSectors) { if (length > SectorBytes * numSectors) {
Debugln("READ", x, z, "invalid length: " + length + " > 4096 * " + numSectors); Debugln("READ", x, z, "invalid length: " + length + " > 4096 * " + numSectors);
return null; return null;
} }
@ -270,13 +280,15 @@ namespace Substrate.Core
Debugln("READ", x, z, "unknown version " + version); Debugln("READ", x, z, "unknown version " + version);
return null; return null;
} catch (IOException) { }
catch (IOException) {
Debugln("READ", x, z, "exception"); Debugln("READ", x, z, "exception");
return null; return null;
} }
} }
public Stream GetChunkDataOutputStream(int x, int z) { public Stream GetChunkDataOutputStream (int x, int z)
{
if (OutOfBounds(x, z)) return null; if (OutOfBounds(x, z)) return null;
return new ZlibStream(new ChunkBuffer(this, x, z), CompressionMode.Compress); return new ZlibStream(new ChunkBuffer(this, x, z), CompressionMode.Compress);
@ -293,13 +305,14 @@ namespace Substrate.Core
* lets chunk writing be multithreaded by not locking the whole file as a * lets chunk writing be multithreaded by not locking the whole file as a
* chunk is serializing -- only writes when serialization is over * chunk is serializing -- only writes when serialization is over
*/ */
class ChunkBuffer : MemoryStream { class ChunkBuffer : MemoryStream
{
private int x, z; private int x, z;
private RegionFile region; private RegionFile region;
private int? _timestamp; private int? _timestamp;
public ChunkBuffer(RegionFile r, int x, int z) public ChunkBuffer (RegionFile r, int x, int z)
: base(8096) : base(8096)
{ {
this.region = r; this.region = r;
@ -313,7 +326,8 @@ namespace Substrate.Core
_timestamp = timestamp; _timestamp = timestamp;
} }
public override void Close() { public override void Close ()
{
if (_timestamp == null) { if (_timestamp == null) {
region.Write(x, z, this.GetBuffer(), (int)this.Length); region.Write(x, z, this.GetBuffer(), (int)this.Length);
} }
@ -329,7 +343,8 @@ namespace Substrate.Core
} }
/* write a chunk at (x,z) with length bytes of data to disk */ /* write a chunk at (x,z) with length bytes of data to disk */
protected void Write(int x, int z, byte[] data, int length, int timestamp) { protected void Write (int x, int z, byte[] data, int length, int timestamp)
{
if (_disposed) { if (_disposed) {
throw new ObjectDisposedException("RegionFile", "Attempting to use a RegionFile after it has been disposed."); throw new ObjectDisposedException("RegionFile", "Attempting to use a RegionFile after it has been disposed.");
} }
@ -338,7 +353,7 @@ namespace Substrate.Core
int offset = GetOffset(x, z); int offset = GetOffset(x, z);
int sectorNumber = offset >> 8; int sectorNumber = offset >> 8;
int sectorsAllocated = offset & 0xFF; int sectorsAllocated = offset & 0xFF;
int sectorsNeeded = (length + CHUNK_HEADER_SIZE) / SECTOR_BYTES + 1; int sectorsNeeded = (length + CHUNK_HEADER_SIZE) / SectorBytes + 1;
// maximum chunk size is 1MB // maximum chunk size is 1MB
if (sectorsNeeded >= 256) { if (sectorsNeeded >= 256) {
@ -349,7 +364,8 @@ namespace Substrate.Core
/* we can simply overwrite the old sectors */ /* we can simply overwrite the old sectors */
Debug("SAVE", x, z, length, "rewrite"); Debug("SAVE", x, z, length, "rewrite");
Write(sectorNumber, data, length); Write(sectorNumber, data, length);
} else { }
else {
/* we need to allocate new sectors */ /* we need to allocate new sectors */
/* mark the sectors previously used for this chunk as free */ /* mark the sectors previously used for this chunk as free */
@ -365,7 +381,8 @@ namespace Substrate.Core
if (runLength != 0) { if (runLength != 0) {
if (sectorFree[i]) runLength++; if (sectorFree[i]) runLength++;
else runLength = 0; else runLength = 0;
} else if (sectorFree[i]) { }
else if (sectorFree[i]) {
runStart = i; runStart = i;
runLength = 1; runLength = 1;
} }
@ -384,7 +401,8 @@ namespace Substrate.Core
sectorFree[sectorNumber + i] = false; sectorFree[sectorNumber + i] = false;
} }
Write(sectorNumber, data, length); Write(sectorNumber, data, length);
} else { }
else {
/* /*
* no free space large enough found -- we need to grow the * no free space large enough found -- we need to grow the
* file * file
@ -396,25 +414,28 @@ namespace Substrate.Core
file.Write(emptySector, 0, emptySector.Length); file.Write(emptySector, 0, emptySector.Length);
sectorFree.Add(false); sectorFree.Add(false);
} }
sizeDelta += SECTOR_BYTES * sectorsNeeded; sizeDelta += SectorBytes * sectorsNeeded;
Write(sectorNumber, data, length); Write(sectorNumber, data, length);
SetOffset(x, z, (sectorNumber << 8) | sectorsNeeded); SetOffset(x, z, (sectorNumber << 8) | sectorsNeeded);
} }
} }
SetTimestamp(x, z, timestamp); SetTimestamp(x, z, timestamp);
} catch (IOException e) { }
catch (IOException e) {
Console.WriteLine(e.StackTrace); Console.WriteLine(e.StackTrace);
} }
} }
/* write a chunk data to the region file at specified sector number */ /* write a chunk data to the region file at specified sector number */
private void Write(int sectorNumber, byte[] data, int length) { private void Write (int sectorNumber, byte[] data, int length)
{
Debugln(" " + sectorNumber); Debugln(" " + sectorNumber);
file.Seek(sectorNumber * SECTOR_BYTES, SeekOrigin.Begin); file.Seek(sectorNumber * SectorBytes, SeekOrigin.Begin);
byte[] bytes = BitConverter.GetBytes(length + 1); byte[] bytes = BitConverter.GetBytes(length + 1);
if (BitConverter.IsLittleEndian) {; if (BitConverter.IsLittleEndian) {
;
Array.Reverse(bytes); Array.Reverse(bytes);
} }
file.Write(bytes, 0, 4); // chunk length file.Write(bytes, 0, 4); // chunk length
@ -428,9 +449,9 @@ namespace Substrate.Core
int sectorNumber = offset >> 8; int sectorNumber = offset >> 8;
int sectorsAllocated = offset & 0xFF; int sectorsAllocated = offset & 0xFF;
file.Seek(sectorNumber * SECTOR_BYTES, SeekOrigin.Begin); file.Seek(sectorNumber * SectorBytes, SeekOrigin.Begin);
for (int i = 0; i < sectorsAllocated; i++) { for (int i = 0; i < sectorsAllocated; i++) {
file.Write(emptySector, 0, SECTOR_BYTES); file.Write(emptySector, 0, SectorBytes);
} }
SetOffset(x, z, 0); SetOffset(x, z, 0);
@ -443,27 +464,32 @@ namespace Substrate.Core
return x < 0 || x >= 32 || z < 0 || z >= 32; return x < 0 || x >= 32 || z < 0 || z >= 32;
} }
private int GetOffset(int x, int z) { private int GetOffset (int x, int z)
{
return offsets[x + z * 32]; return offsets[x + z * 32];
} }
public bool HasChunk(int x, int z) { public bool HasChunk (int x, int z)
{
return GetOffset(x, z) != 0; return GetOffset(x, z) != 0;
} }
private void SetOffset(int x, int z, int offset) { private void SetOffset (int x, int z, int offset)
{
offsets[x + z * 32] = offset; offsets[x + z * 32] = offset;
file.Seek((x + z * 32) * 4, SeekOrigin.Begin); file.Seek((x + z * 32) * 4, SeekOrigin.Begin);
byte[] bytes = BitConverter.GetBytes(offset); byte[] bytes = BitConverter.GetBytes(offset);
if (BitConverter.IsLittleEndian) {; if (BitConverter.IsLittleEndian) {
;
Array.Reverse(bytes); Array.Reverse(bytes);
} }
file.Write(bytes, 0, 4); file.Write(bytes, 0, 4);
} }
private int Timestamp () { private int Timestamp ()
{
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0); DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0);
return (int)((DateTime.UtcNow - epoch).Ticks / (10000L * 1000L)); return (int)((DateTime.UtcNow - epoch).Ticks / (10000L * 1000L));
} }
@ -479,20 +505,38 @@ namespace Substrate.Core
return chunkTimestamps[x + z * 32]; return chunkTimestamps[x + z * 32];
} }
public void SetTimestamp(int x, int z, int value) { public void SetTimestamp (int x, int z, int value)
{
chunkTimestamps[x + z * 32] = value; chunkTimestamps[x + z * 32] = value;
file.Seek(SECTOR_BYTES + (x + z * 32) * 4, SeekOrigin.Begin); file.Seek(SectorBytes + (x + z * 32) * 4, SeekOrigin.Begin);
byte[] bytes = BitConverter.GetBytes(value); byte[] bytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian) {; if (BitConverter.IsLittleEndian) {
;
Array.Reverse(bytes); Array.Reverse(bytes);
} }
file.Write(bytes, 0, 4); file.Write(bytes, 0, 4);
} }
public void Close() { public void Close ()
{
file.Close(); file.Close();
} }
protected virtual int SectorBytes
{
get { return SECTOR_BYTES; }
}
protected virtual int SectorInts
{
get { return SECTOR_BYTES / 4; }
}
protected virtual byte[] EmptySector
{
get { return emptySector; }
}
} }
} }