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(ChunkGlobalX(lcx), ChunkGlobalZ(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; } } }