using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using Substrate.Nbt;
using Substrate.Core;
namespace Substrate
{
///
/// Represents a single region containing 32x32 chunks.
///
public class Region : IDisposable, IChunkContainer
{
private const int XDIM = 32;
private const int ZDIM = 32;
private const int XMASK = XDIM - 1;
private const int ZMASK = ZDIM - 1;
private const int XLOG = 5;
private const int ZLOG = 5;
private int _rx;
private int _rz;
private bool _disposed = false;
private RegionManager _regionMan;
private static Regex _namePattern = new Regex("r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mcr$");
private WeakReference _regionFile;
private ChunkCache _cache;
///
/// Gets the global X-coordinate of the region.
///
public int X
{
get { return _rx; }
}
///
/// Gets the global Z-coordinate of the region.
///
public int Z
{
get { return _rz; }
}
///
/// Gets the length of the X-dimension of the region in chunks.
///
public int XDim
{
get { return XDIM; }
}
///
/// Gets the length of the Z-dimension of the region in chunks.
///
public int ZDim
{
get { return ZDIM; }
}
///
/// Creates an instance of a for a given set of coordinates.
///
/// The that should be managing this region.
/// A shared cache for holding chunks.
/// The global X-coordinate of the region.
/// The global Z-coordinate of the region.
/// The constructor will not actually open or parse any region files. Given just the region coordinates, the
/// region will be able to determien the correct region file to look for based on the naming pattern for regions:
/// r.x.z.mcr, given x and z are integers representing the region's coordinates.
/// Regions require a to be provided because they do not actually store any chunks or references
/// to chunks on their own. This allows regions to easily pass off requests outside of their bounds, if necessary.
public Region (RegionManager rm, ChunkCache cache, int rx, int rz)
{
_regionMan = rm;
_cache = cache;
_regionFile = new WeakReference(null);
_rx = rx;
_rz = rz;
if (!File.Exists(GetFilePath())) {
throw new FileNotFoundException();
}
}
///
/// Creates an instance of a for the given region file.
///
/// The that should be managing this region.
/// A shared cache for holding chunks.
/// The region file to derive the region from.
/// The constructor will not actually open or parse the region file. It will only read the file's name in order
/// to derive the region's coordinates, based on a strict naming pattern for regions: r.x.z.mcr, given x and z are integers
/// representing the region's coordinates.
/// Regions require a to be provided because they do not actually store any chunks or references
/// to chunks on their own. This allows regions to easily pass off requests outside of their bounds, if necessary.
public Region (RegionManager rm, ChunkCache cache, string filename)
{
_regionMan = rm;
_cache = cache;
_regionFile = new WeakReference(null);
ParseFileName(filename, out _rx, out _rz);
if (!File.Exists(Path.Combine(_regionMan.GetRegionPath(), filename))) {
throw new FileNotFoundException();
}
}
///
/// Region finalizer that ensures any resources are cleaned up
///
~Region ()
{
Dispose(false);
}
///
/// Disposes any managed and unmanaged resources held by the region.
///
public void Dispose ()
{
Dispose(true);
System.GC.SuppressFinalize(this);
}
///
/// Conditionally dispose managed or unmanaged resources.
///
/// True if the call to Dispose was explicit.
protected virtual void Dispose (bool disposing)
{
if (!_disposed) {
if (disposing) {
// Cleanup managed resources
RegionFile rf = _regionFile.Target as RegionFile;
if (rf != null) {
rf.Dispose();
rf = null;
}
}
// Cleanup unmanaged resources
}
_disposed = true;
}
///
/// Get the appropriate filename for this region.
///
/// The filename of the region with encoded coordinates.
public string GetFileName ()
{
return "r." + _rx + "." + _rz + ".mcr";
}
///
/// Tests if the given filename conforms to the general naming pattern for any region.
///
/// The filename to test.
/// True if the filename is a valid region name; false if it does not conform to the pattern.
public static bool TestFileName (string filename)
{
Match match = _namePattern.Match(filename);
if (!match.Success) {
return false;
}
return true;
}
///
/// Parses the given filename to extract encoded region coordinates.
///
/// The region filename to parse.
/// This parameter will contain the X-coordinate of a region.
/// This parameter will contain the Z-coordinate of a region.
/// True if the filename could be correctly parse; false otherwise.
public static bool ParseFileName (string filename, out int x, out int z)
{
x = 0;
z = 0;
Match match = _namePattern.Match(filename);
if (!match.Success) {
return false;
}
x = Convert.ToInt32(match.Groups[1].Value);
z = Convert.ToInt32(match.Groups[2].Value);
return true;
}
///
/// Gets the full path of the region's backing file.
///
/// Gets the path of the region's file based on the 's region path and the region's on filename.
public string GetFilePath ()
{
return System.IO.Path.Combine(_regionMan.GetRegionPath(), GetFileName());
}
private RegionFile GetRegionFile ()
{
RegionFile rf = _regionFile.Target as RegionFile;
if (rf == null) {
rf = new RegionFile(GetFilePath());
_regionFile.Target = rf;
}
return rf;
}
///
/// Gets the for a chunk given local coordinates into the region.
///
/// The local X-coordinate of a chunk within the region.
/// The local Z-coordinate of a chunk within the region.
/// An for a local chunk, or null if there is no chunk at the given coordinates.
public NbtTree GetChunkTree (int lcx, int lcz)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
return (alt == null) ? null : alt.GetChunkTree(ForeignX(lcx), ForeignZ(lcz));
}
RegionFile rf = GetRegionFile();
Stream nbtstr = rf.GetChunkDataInputStream(lcx, lcz);
if (nbtstr == null) {
return null;
}
NbtTree tree = new NbtTree(nbtstr);
nbtstr.Close();
return tree;
}
// XXX: Exceptions
///
/// Saves an for a chunk back to the region's data store at the given local coordinates.
///
/// The local X-coordinate of the chunk within the region.
/// The local Z-coordinate of the chunk within the region.
/// The of a chunk to write back to the region.
/// True if the save succeeded.
/// It is up to the programmer to ensure that the global coordinates defined within the chunk's tree
/// are consistent with the local coordinates of the region being written into.
public bool SaveChunkTree (int lcx, int lcz, NbtTree tree)
{
return SaveChunkTree(lcx, lcz, tree, null);
}
///
/// Saves an for a chunk back to the region's data store at the given local coordinates and with the given timestamp.
///
/// The local X-coordinate of the chunk within the region.
/// The local Z-coordinate of the chunk within the region.
/// The of a chunk to write back to the region.
/// The timestamp to write to the underlying region file for this chunk.
/// True if the save succeeded.
/// It is up to the programmer to ensure that the global coordinates defined within the chunk's tree
/// are consistent with the local coordinates of the region being written into.
public bool SaveChunkTree (int lcx, int lcz, NbtTree tree, int timestamp)
{
return SaveChunkTree(lcx, lcz, tree, timestamp);
}
private bool SaveChunkTree (int lcx, int lcz, NbtTree tree, int? timestamp)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
return (alt == null) ? false : alt.SaveChunkTree(ForeignX(lcx), ForeignZ(lcz), tree);
}
RegionFile rf = GetRegionFile();
Stream zipstr = (timestamp == null)
? rf.GetChunkDataOutputStream(lcx, lcz)
: rf.GetChunkDataOutputStream(lcx, lcz, (int)timestamp);
if (zipstr == null) {
return false;
}
tree.WriteTo(zipstr);
zipstr.Close();
return true;
}
///
/// Gets an output stream for replacing chunk data at the given coordinates within the region.
///
/// The local X-coordinate of the chunk to replace within the region.
/// The local Z-coordinate of the chunk to replace within the region.
/// An output stream that can be written to on demand.
/// There is no guarantee that any data will be saved until the stream is closed.
public Stream GetChunkOutStream (int lcx, int lcz)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
return (alt == null) ? null : alt.GetChunkOutStream(ForeignX(lcx), ForeignZ(lcz));
}
RegionFile rf = GetRegionFile();
return rf.GetChunkDataOutputStream(lcx, lcz);
}
///
/// Returns the count of valid chunks stored in this region.
///
/// The count of currently stored chunks.
public int ChunkCount ()
{
RegionFile rf = GetRegionFile();
int count = 0;
for (int x = 0; x < XDIM; x++) {
for (int z = 0; z < ZDIM; z++) {
if (rf.HasChunk(x, z)) {
count++;
}
}
}
return count;
}
// XXX: Consider revising foreign lookup support
///
/// Gets a for a chunk at the given local coordinates relative to this region.
///
/// The local X-coordinate of a chunk relative to this region.
/// The local Z-coordinate of a chunk relative to this region.
/// A at the given local coordinates, or null if no chunk exists.
/// The local coordinates do not strictly need to be within the bounds of the region. If coordinates are detected
/// as being out of bounds, the lookup will be delegated to the correct region and the lookup will be performed there
/// instead. This allows any to perform a similar task to , but with a
/// region-local frame of reference instead of a global frame of reference.
public ChunkRef GetChunkRef (int lcx, int lcz)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
return (alt == null) ? null : alt.GetChunkRef(ForeignX(lcx), ForeignZ(lcz));
}
int cx = lcx + _rx * XDIM;
int cz = lcz + _rz * ZDIM;
ChunkKey k = new ChunkKey(cx, cz);
ChunkRef c = _cache.Fetch(k);
if (c != null) {
return c;
}
c = ChunkRef.Create(this, lcx, lcz);
if (c != null) {
_cache.Insert(c);
}
return c;
}
///
/// Creates a new chunk at the given local coordinates relative to this region and returns a new for it.
///
/// The local X-coordinate of a chunk relative to this region.
/// The local Z-coordinate of a chunk relative to this region.
/// A for the newly created chunk.
/// If the local coordinates are out of bounds for this region, the action will be forwarded to the correct region
/// transparently.
public ChunkRef CreateChunk (int lcx, int lcz)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
return (alt == null) ? null : alt.CreateChunk(ForeignX(lcx), ForeignZ(lcz));
}
DeleteChunk(lcx, lcz);
int cx = lcx + _rx * XDIM;
int cz = lcz + _rz * ZDIM;
Chunk c = Chunk.Create(cx, cz);
c.Save(GetChunkOutStream(lcx, lcz));
ChunkRef cr = ChunkRef.Create(this, lcx, lcz);
_cache.Insert(cr);
return cr;
}
#region IChunkCollection Members
// XXX: This also feels dirty.
///
/// Gets the global X-coordinate of a chunk given an internal coordinate handed out by a container.
///
/// An internal X-coordinate given to a by any instance of a container.
/// The global X-coordinate of the corresponding chunk.
public int ChunkGlobalX (int cx)
{
return _rx * XDIM + cx;
}
///
/// Gets the global Z-coordinate of a chunk given an internal coordinate handed out by a container.
///
/// An internal Z-coordinate given to a by any instance of a container.
/// The global Z-coordinate of the corresponding chunk.
public int ChunkGlobalZ (int cz)
{
return _rz * ZDIM + cz;
}
///
/// Gets the region-local X-coordinate of a chunk given an internal coordinate handed out by a container.
///
/// An internal X-coordinate given to a by any instance of a container.
/// The region-local X-coordinate of the corresponding chunk.
public int ChunkLocalX (int cx)
{
return cx;
}
///
/// Gets the region-local Z-coordinate of a chunk given an internal coordinate handed out by a container.
///
/// An internal Z-coordinate given to a by any instance of a container.
/// The region-local Z-coordinate of the corresponding chunk.
public int ChunkLocalZ (int cz)
{
return cz;
}
///
/// Returns a given local coordinates relative to this region.
///
/// The local X-coordinate of a chunk relative to this region.
/// The local Z-coordinate of a chunk relative to this region.
/// A object for the given coordinates, or null if the chunk does not exist.
/// If the local coordinates are out of bounds for this region, the action will be forwarded to the correct region
/// transparently. The returned object may either come from cache, or be regenerated from disk.
public Chunk GetChunk (int lcx, int lcz)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
return (alt == null) ? null : alt.GetChunk(ForeignX(lcx), ForeignZ(lcz));
}
if (!ChunkExists(lcx, lcz)) {
return null;
}
return Chunk.CreateVerified(GetChunkTree(lcx, lcz));
}
///
/// Checks if a chunk exists at the given local coordinates relative to this region.
///
/// The local X-coordinate of a chunk relative to this region.
/// The local Z-coordinate of a chunk relative to this region.
/// True if there is a chunk at the given coordinates; false otherwise.
/// If the local coordinates are out of bounds for this region, the action will be forwarded to the correct region
/// transparently.
public bool ChunkExists (int lcx, int lcz)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
return (alt == null) ? false : alt.ChunkExists(ForeignX(lcx), ForeignZ(lcz));
}
RegionFile rf = GetRegionFile();
return rf.HasChunk(lcx, lcz);
}
///
/// Deletes a chunk from the underlying data store at the given local coordinates relative to this region.
///
/// The local X-coordinate of a chunk relative to this region.
/// The local Z-coordinate of a chunk relative to this region.
/// True if there is a chunk was deleted; false otherwise.
/// If the local coordinates are out of bounds for this region, the action will be forwarded to the correct region
/// transparently.
public bool DeleteChunk (int lcx, int lcz)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
return (alt == null) ? false : alt.DeleteChunk(ForeignX(lcx), ForeignZ(lcz));
}
RegionFile rf = GetRegionFile();
if (!rf.HasChunk(lcx, lcz)) {
return false;
}
rf.DeleteChunk(lcx, lcz);
ChunkKey k = new ChunkKey(lcx, lcz);
_cache.Remove(k);
if (ChunkCount() == 0) {
_regionMan.DeleteRegion(X, Z);
}
return true;
}
///
/// Saves an existing to the region at the given local coordinates.
///
/// The local X-coordinate of a chunk relative to this region.
/// The local Z-coordinate of a chunk relative to this region.
/// A to save to the given location.
/// A represneting the at its new location.
/// If the local coordinates are out of bounds for this region, the action will be forwarded to the correct region
/// transparently. The 's internal global coordinates will be updated to reflect the new location.
public ChunkRef SetChunk (int lcx, int lcz, Chunk chunk)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
return (alt == null) ? null : alt.CreateChunk(ForeignX(lcx), ForeignZ(lcz));
}
DeleteChunk(lcx, lcz);
int cx = lcx + _rx * XDIM;
int cz = lcz + _rz * ZDIM;
chunk.SetLocation(cx, cz);
chunk.Save(GetChunkOutStream(lcx, lcz));
ChunkRef cr = ChunkRef.Create(this, lcx, lcz);
_cache.Insert(cr);
return cr;
}
///
/// Saves all chunks within this region that have been marked as dirty.
///
/// The number of chunks that were saved.
public int Save ()
{
_cache.SyncDirty();
int saved = 0;
IEnumerator en = _cache.GetDirtyEnumerator();
while (en.MoveNext()) {
ChunkRef chunk = en.Current;
if (!ChunkExists(chunk.LocalX, chunk.LocalZ)) {
throw new MissingChunkException();
}
if (chunk.Save(GetChunkOutStream(chunk.LocalX, chunk.LocalZ))) {
saved++;
}
}
_cache.ClearDirty();
return saved;
}
// XXX: Allows a chunk not part of this region to be saved to it
///
public bool SaveChunk (Chunk chunk)
{
//Console.WriteLine("Region[{0}, {1}].Save({2}, {3})", _rx, _rz, ForeignX(chunk.X),ForeignZ(chunk.Z));
return chunk.Save(GetChunkOutStream(ForeignX(chunk.X), ForeignZ(chunk.Z)));
}
///
/// Checks if this container supports delegating an action on out-of-bounds coordinates to another container.
///
public bool CanDelegateCoordinates
{
get { return true; }
}
///
/// Gets the timestamp of a chunk from the underlying region file.
///
/// The local X-coordinate of a chunk relative to this region.
/// The local Z-coordinate of a chunk relative to this region.
/// The timestamp of the chunk slot in the region.
/// The value returned may differ from any timestamp stored in the chunk data itself.
public int GetChunkTimestamp (int lcx, int lcz)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
return (alt == null) ? 0 : alt.GetChunkTimestamp(ForeignX(lcx), ForeignZ(lcz));
}
RegionFile rf = GetRegionFile();
return rf.GetTimestamp(lcx, lcz);
}
///
/// Sets the timestamp of a chunk in the underlying region file.
///
/// The local X-coordinate of a chunk relative to this region.
/// The local Z-coordinate of a chunk relative to this region.
/// The new timestamp value.
/// This function will only update the timestamp of the chunk slot in the underlying region file. It will not update
/// any timestamp information in the chunk data itself.
public void SetChunkTimestamp (int lcx, int lcz, int timestamp)
{
if (!LocalBoundsCheck(lcx, lcz)) {
Region alt = GetForeignRegion(lcx, lcz);
if (alt != null)
alt.SetChunkTimestamp(ForeignX(lcx), ForeignZ(lcz), timestamp);
}
RegionFile rf = GetRegionFile();
rf.SetTimestamp(lcx, lcz, timestamp);
}
#endregion
private bool LocalBoundsCheck (int lcx, int lcz)
{
return (lcx >= 0 && lcx < XDIM && lcz >= 0 && lcz < ZDIM);
}
private Region GetForeignRegion (int lcx, int lcz)
{
return _regionMan.GetRegion(_rx + (lcx >> XLOG), _rz + (lcz >> ZLOG));
}
private int ForeignX (int lcx)
{
return (lcx + XDIM * 10000) & XMASK;
}
private int ForeignZ (int lcz)
{
return (lcz + ZDIM * 10000) & ZMASK;
}
}
}