from __future__ import annotations
from math import log
import sys
import gzip
from io import StringIO
from typing import Tuple
from numpy import ndarray, zeros, uint8
Coordinates = Tuple[int, int]
SECTOR_BYTES = 4096
SECTOR_INTS = SECTOR_BYTES / 4
CHUNK_HEADER_SIZE = 5
VERSION_GZIP = 1
VERSION_DEFLATE = 2
[docs]def block_coords_to_chunk_coords(x: int, z: int) -> Coordinates:
"""
Converts the supplied block coordinates into chunk coordinates
:param x: The x coordinate of the block
:param z: The z coordinate of the block
:return: The resulting chunk coordinates in (x, z) order
"""
return x >> 4, z >> 4
[docs]def chunk_coords_to_block_coords(x: int, z: int) -> Coordinates:
"""
Converts the supplied chunk coordinates into block coordinates
:param x: The x coordinate of the chunk
:param z: The z coordinate of the chunk
:return: The resulting block coordinates in (x, z) order
"""
return x << 4, z << 4
[docs]def chunk_coords_to_region_coords(cx: int, cz: int) -> Coordinates:
"""
Converts the supplied chunk coordinates into region coordinates
:param cx: The x coordinate of the chunk
:param cz: The z coordinate of the chunk
:return: The resulting region coordinates in (x, z) order
"""
return cx >> 5, cz >> 5
[docs]def region_coords_to_chunk_coords(rx: int, rz: int) -> Coordinates:
"""
Converts the supplied region coordinates into the minimum chunk coordinates of that region
:param rx: The x coordinate of the region
:param rz: The y coordinate of the region
:return: The resulting minimum chunk coordinates of that region in (x, z) order
"""
return rx << 5, rz << 5
[docs]def blocks_slice_to_chunk_slice(blocks_slice: slice) -> slice:
"""
Converts the supplied blocks slice into chunk slice
:param blocks_slice: The slice of the blocks
:return: The resulting chunk slice
"""
return slice(blocks_slice.start % 16, blocks_slice.stop % 16)
[docs]def gunzip(data):
"""
Decompresses data that is in Gzip format
"""
return gzip.GzipFile(fileobj=StringIO(data)).read()
[docs]def from_nibble_array(arr: ndarray) -> ndarray:
"""
Unpacks a nibble array into a full size numpy array
:param arr: The nibble array
:return: The resulting array
"""
shape = arr.shape
new_arr = zeros((shape[0], shape[1], shape[2] * 2), dtype=uint8)
new_arr[:, :, ::2] = arr
new_arr[:, :, ::2] &= 0xF
new_arr[:, :, 1::2] = arr
new_arr[:, :, 1::2] >>= 4
return new_arr
[docs]def get_size(obj, seen=None):
"""Recursively finds size of objects"""
size = sys.getsizeof(obj)
if seen is None:
seen = set()
obj_id = id(obj)
if obj_id in seen:
return 0
# Important mark as seen *before* entering recursion to gracefully handle
# self-referential objects
seen.add(obj_id)
if isinstance(obj, dict):
size += sum([get_size(v, seen) for v in obj.values()])
size += sum([get_size(k, seen) for k in obj.keys()])
elif hasattr(obj, "__dict__"):
size += get_size(obj.__dict__, seen)
elif hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, bytearray)):
size += sum([get_size(i, seen) for i in obj])
return size
[docs]def get_smallest_dtype(arr: ndarray, uint: bool = True) -> int:
"""
Returns the smallest dtype (number) that the array can afford
:param arr: The array to check on
:param uint: Should the array fit in uint or not (default: True)
:return: The number of bits all the elements can be represented with
"""
possible_dtypes = (2 ** x for x in range(3, 8))
max_number = arr.max()
if not uint:
max_number = max_number * 2
if max_number == 0:
max_number = 1
return next(dtype for dtype in possible_dtypes if dtype > log(max_number, 2))