From 8ff6052d770803dcbedf95d74bbf560f82099ac6 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 11 Aug 2020 10:52:57 +0100 Subject: [PATCH 01/10] Added a sub-chunk size property --- amulet/api/structure.py | 12 ++++++------ amulet/api/world.py | 17 +++++++++++++---- amulet/api/wrapper/world_format_wrapper.py | 6 +++++- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/amulet/api/structure.py b/amulet/api/structure.py index 373149444..66225b769 100644 --- a/amulet/api/structure.py +++ b/amulet/api/structure.py @@ -89,7 +89,7 @@ def get_chunk(self, cx: int, cz: int) -> Chunk: raise ChunkDoesNotExist def get_block(self, x: int, y: int, z: int) -> Block: - cx, cz = block_coords_to_chunk_coords(x, z, chunk_size=self.chunk_size[0]) + cx, cz = block_coords_to_chunk_coords(x, z, chunk_size=self.sub_chunk_size) offset_x, offset_z = x - 16 * cx, z - 16 * cz chunk = self.get_chunk(cx, cz) @@ -113,10 +113,10 @@ def get_chunk_boxes( selection: SelectionGroup for box in selection.selection_boxes: first_chunk = block_coords_to_chunk_coords( - box.min_x, box.min_z, chunk_size=self.chunk_size[0] + box.min_x, box.min_z, chunk_size=self.sub_chunk_size ) last_chunk = block_coords_to_chunk_coords( - box.max_x - 1, box.max_z - 1, chunk_size=self.chunk_size[0] + box.max_x - 1, box.max_z - 1, chunk_size=self.sub_chunk_size ) for cx, cz in itertools.product( range(first_chunk[0], last_chunk[0] + 1), @@ -140,7 +140,7 @@ def get_chunk_slices( chunk.blocks[slice] = ... """ for chunk, box in self.get_chunk_boxes(selection): - slices = box.chunk_slice(chunk.cx, chunk.cz, self.chunk_size[0]) + slices = box.chunk_slice(chunk.cx, chunk.cz, self.sub_chunk_size) yield chunk, slices, box def get_moved_chunk_slices( @@ -199,8 +199,8 @@ def get_moved_chunk_slices( chunk_box = self._chunk_box(cx, cz, destination_chunk_shape) dst_box = chunk_box.intersection(dst_full_box) src_box = SelectionBox(-offset + dst_box.min, -offset + dst_box.max) - src_slices = src_box.chunk_slice(chunk.cx, chunk.cz, self.chunk_size[0]) - dst_slices = dst_box.chunk_slice(cx, cz, self.chunk_size[0]) + src_slices = src_box.chunk_slice(chunk.cx, chunk.cz, self.sub_chunk_size) + dst_slices = dst_box.chunk_slice(cx, cz, self.sub_chunk_size) yield chunk, src_slices, src_box, (cx, cz), dst_slices, dst_box def transform(self, scale: FloatTriplet, rotation: FloatTriplet) -> "Structure": diff --git a/amulet/api/world.py b/amulet/api/world.py index 2a842d46f..72e706e88 100644 --- a/amulet/api/world.py +++ b/amulet/api/world.py @@ -26,9 +26,13 @@ class BaseStructure: + @property + def sub_chunk_size(self) -> int: + return 16 + @property def chunk_size(self) -> Tuple[int, int, int]: - return 16, 256, 16 + return self.sub_chunk_size, self.sub_chunk_size * 16, self.sub_chunk_size def get_chunk(self, *args, **kwargs) -> Chunk: raise NotImplementedError @@ -111,7 +115,12 @@ def create_undo_point(self): self._chunk_history_manager.create_undo_point(self._chunk_cache) @property - def chunk_size(self) -> Tuple[int, int, int]: + def sub_chunk_size(self) -> int: + """The normal dimensions of the chunk""" + return self._world_wrapper.sub_chunk_size + + @property + def chunk_size(self) -> Tuple[int, Union[int, None], int]: """The normal dimensions of the chunk""" return self._world_wrapper.chunk_size @@ -334,7 +343,7 @@ def get_chunk_boxes( if isinstance(selection, SelectionBox): selection = SelectionGroup([selection]) selection: SelectionGroup - for (cx, cz), box in selection.sub_sections(self.chunk_size[0]): + for (cx, cz), box in selection.sub_sections(self.sub_chunk_size): try: chunk = self.get_chunk(cx, cz, dimension) except ChunkDoesNotExist: @@ -366,7 +375,7 @@ def get_chunk_slices( for chunk, box in self.get_chunk_boxes( selection, dimension, create_missing_chunks ): - slices = box.chunk_slice(chunk.cx, chunk.cz, self.chunk_size[0]) + slices = box.chunk_slice(chunk.cx, chunk.cz, self.sub_chunk_size) yield chunk, slices, box # def get_entities_in_box( diff --git a/amulet/api/wrapper/world_format_wrapper.py b/amulet/api/wrapper/world_format_wrapper.py index e500e5885..626db7ea7 100644 --- a/amulet/api/wrapper/world_format_wrapper.py +++ b/amulet/api/wrapper/world_format_wrapper.py @@ -24,9 +24,13 @@ def __init__(self, world_path: str): self._world_image_path = missing_world_icon self._changed: bool = False + @property + def sub_chunk_size(self) -> int: + return 16 + @property def chunk_size(self) -> Tuple[int, Union[int, None], int]: - return 16, 256, 16 + return self.sub_chunk_size, self.sub_chunk_size * 16, self.sub_chunk_size @property def path(self) -> str: From bfce3c1cb3b740f5290cdedcbec55a1549aa9440 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 11 Aug 2020 10:55:40 +0100 Subject: [PATCH 02/10] Added a get and set block method in the chunk --- amulet/api/chunk/chunk.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/amulet/api/chunk/chunk.py b/amulet/api/chunk/chunk.py index af3ed0627..e8c5e7b0a 100644 --- a/amulet/api/chunk/chunk.py +++ b/amulet/api/chunk/chunk.py @@ -6,6 +6,7 @@ import pickle import gzip +from amulet.api.block import Block from amulet.api.registry import BlockManager from amulet.api.registry.biome_manager import BiomeManager from amulet.api.chunk import Biomes, Blocks, Status, BlockEntityDict, EntityList @@ -119,6 +120,27 @@ def blocks(self) -> Blocks: def blocks(self, value: Optional[Union[Dict[int, numpy.ndarray], Blocks]]): self._blocks = Blocks(value) + def get_block(self, dx: int, y: int, dz: int) -> Block: + """ + Get the universal Block object at the given location within the chunk. + :param dx: The x coordinate within the chunk. 0 is the bottom edge, 15 is the top edge + :param y: The y coordinate within the chunk. This can be any integer. + :param dz: The z coordinate within the chunk. 0 is the bottom edge, 15 is the top edge + :return: The universal Block object representation of the block at that location + """ + return self.block_palette[self.blocks[dx, y, dz]] + + def set_block(self, dx: int, y: int, dz: int, block: Block): + """ + Get the universal Block object at the given location within the chunk. + :param dx: The x coordinate within the chunk. 0 is the bottom edge, 15 is the top edge + :param y: The y coordinate within the chunk. This can be any integer. + :param dz: The z coordinate within the chunk. 0 is the bottom edge, 15 is the top edge + :param block: The universal Block object to set at the given location + :return: + """ + self.blocks[dx, y, dz] = self.block_palette.get_add_block(block) + @property def _block_palette(self) -> BlockManager: """The block block_palette for the chunk. From 11509f21fd243ab8d53270d1435fb10fefab948c Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 11 Aug 2020 11:28:49 +0100 Subject: [PATCH 03/10] Fixed block entities being set at the wrong location --- amulet/api/chunk/block_entity_dict.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/amulet/api/chunk/block_entity_dict.py b/amulet/api/chunk/block_entity_dict.py index d9f0e0efd..ee3814a7b 100644 --- a/amulet/api/chunk/block_entity_dict.py +++ b/amulet/api/chunk/block_entity_dict.py @@ -95,19 +95,21 @@ def __delitem__(self, coordinate: Coordinate) -> None: self._dirty() super().__delitem__(coordinate) - def __setitem__(self, coordinate: Coordinate, block_entity: BlockEntity) -> None: - """ Set self[key] to value. """ + def _check_block_entity(self, coordinate: Coordinate, block_entity: BlockEntity) -> BlockEntity: self._assert_key(coordinate) self._assert_val(block_entity) - self._dirty() - self.data[coordinate] = block_entity + if coordinate != block_entity.location: + block_entity = block_entity.new_at_location(*coordinate) + return block_entity + + def __setitem__(self, coordinate: Coordinate, block_entity: BlockEntity) -> None: + """ Set self[key] to value. """ + self.data[coordinate] = self._check_block_entity(coordinate, block_entity) def setdefault( self, coordinate: Coordinate, block_entity: BlockEntity ) -> BlockEntity: - self._assert_key(coordinate) - self._assert_val(block_entity) - self._dirty() + block_entity = self._check_block_entity(coordinate, block_entity) return self.data.setdefault(coordinate, block_entity) def popitem(self): From 8d2f7f4342b157a4fad69566419a2992bdf23d7c Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 11 Aug 2020 11:33:40 +0100 Subject: [PATCH 04/10] Added methods to the world class to get and set the block from version data --- amulet/api/world.py | 85 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/amulet/api/world.py b/amulet/api/world.py index 72e706e88..f02542b8c 100644 --- a/amulet/api/world.py +++ b/amulet/api/world.py @@ -8,6 +8,8 @@ from amulet import log from .block import Block +from .block_entity import BlockEntity +from .entity import Entity from amulet.api.registry import BlockManager from amulet.api.registry.biome_manager import BiomeManager from .errors import ChunkDoesNotExist, ChunkLoadError, LevelDoesNotExist @@ -15,7 +17,7 @@ from .chunk import Chunk from .selection import SelectionGroup, SelectionBox from .paths import get_temp_dir -from .data_types import OperationType, Dimension, DimensionCoordinates +from .data_types import OperationType, Dimension, DimensionCoordinates, VersionIdentifierType from ..utils.world_utils import block_coords_to_chunk_coords if TYPE_CHECKING: @@ -297,6 +299,11 @@ def get_chunk(self, cx: int, cz: int, dimension: Dimension) -> Chunk: return chunk + def create_chunk(self, cx: int, cz: int, dimension: Dimension) -> Chunk: + chunk = Chunk(cx, cz) + self.put_chunk(chunk, dimension) + return chunk + def put_chunk(self, chunk: Chunk, dimension: Dimension): """Add a chunk to the universal world database""" chunk.changed = True @@ -311,21 +318,87 @@ def delete_chunk(self, cx: int, cz: int, dimension: Dimension): def get_block(self, x: int, y: int, z: int, dimension: Dimension) -> Block: """ - Gets the blockstate at the specified coordinates + Gets the universal Block object at the specified coordinates :param x: The X coordinate of the desired block :param y: The Y coordinate of the desired block :param z: The Z coordinate of the desired block :param dimension: The dimension of the desired block - :return: The blockstate name as a string + :return: The universal Block object representation of the block at that location + :raise: Raises ChunkDoesNotExist or ChunkLoadError if the chunk was not loaded. """ - # TODO: move this logic into the chunk class and have this method call that cx, cz = block_coords_to_chunk_coords(x, z, chunk_size=self.chunk_size[0]) offset_x, offset_z = x - 16 * cx, z - 16 * cz + return self.get_chunk(cx, cz, dimension).get_block(offset_x, y, offset_z) + + def get_version_block( + self, + x: int, + y: int, + z: int, + dimension: Dimension, + version: VersionIdentifierType, + ) -> Tuple[Union[Block, Entity], Optional[BlockEntity]]: + """ + Get a block at the specified location and convert it to the format of the version specified + Note the odd return format. In most cases this will return (Block, None) or (Block, BlockEntity) + but in select cases like item frames may return (Entity, None) + + :param x: The X coordinate of the desired block + :param y: The Y coordinate of the desired block + :param z: The Z coordinate of the desired block + :param dimension: The dimension of the desired block + :param version: The version to get the block converted to. + :return: The block at the given location converted to the `version` format. Note the odd return format. + :raise: Raises ChunkDoesNotExist or ChunkLoadError if the chunk was not loaded. + """ + cx, cz = block_coords_to_chunk_coords(x, z, chunk_size=self.sub_chunk_size) chunk = self.get_chunk(cx, cz, dimension) - block = chunk.blocks[offset_x, y, offset_z] - return self._block_palette[block] + offset_x, offset_z = x - 16 * cx, z - 16 * cz + + output, extra_output, _ = self.translation_manager.get_version(*version).block.from_universal( + chunk.get_block(offset_x, y, offset_z), + chunk.block_entities.get((x, y, z)) + ) + return output, extra_output + + def set_version_block( + self, + x: int, + y: int, + z: int, + dimension: Dimension, + version: VersionIdentifierType, + block: Block, + block_entity: BlockEntity + ): + """ + Convert the block and block_entity from the given version format to the universal format and set at the location + + :param x: The X coordinate of the desired block + :param y: The Y coordinate of the desired block + :param z: The Z coordinate of the desired block + :param dimension: The dimension of the desired block + :param version: The version to get the block converted to. + :param block: + :param block_entity: + :return: The block at the given location converted to the `version` format. Note the odd return format. + :raise: Raises ChunkLoadError if the chunk was not loaded correctly. + """ + cx, cz = block_coords_to_chunk_coords(x, z, chunk_size=self.sub_chunk_size) + try: + chunk = self.get_chunk(cx, cz, dimension) + except ChunkDoesNotExist: + chunk = self.create_chunk(cx, cz, dimension) + offset_x, offset_z = x - 16 * cx, z - 16 * cz + + universal_block, universal_block_entity, _ = self.translation_manager.get_version(*version).block.to_universal( + block, + block_entity + ) + chunk.set_block(offset_x, y, offset_z, block), + chunk.block_entities[(x, y, z)] = block_entity def get_chunk_boxes( self, From e1613ae4cbb79c734aff8544c5798121dcd4a71a Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 11 Aug 2020 11:41:07 +0100 Subject: [PATCH 05/10] Removed the old dirty logic This was causing more issues than it was worth and users need to mark a chunk as dirty anyway --- amulet/api/chunk/block_entity_dict.py | 14 ++------------ amulet/api/chunk/chunk.py | 4 ++-- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/amulet/api/chunk/block_entity_dict.py b/amulet/api/chunk/block_entity_dict.py index ee3814a7b..3bc54a244 100644 --- a/amulet/api/chunk/block_entity_dict.py +++ b/amulet/api/chunk/block_entity_dict.py @@ -15,14 +15,12 @@ class BlockEntityDict(UserDict): InputType = Iterable[BlockEntity] - def __init__(self, parent_chunk: "Chunk", block_entities: InputType = ()): + def __init__(self, block_entities: InputType = ()): super(BlockEntityDict, self).__init__() for block_entity in block_entities: self._assert_val(block_entity) self.data[block_entity.location] = block_entity - self._parent_chunk = weakref.ref(parent_chunk) - def _assert_key(self, key): assert self._check_key( key @@ -41,19 +39,15 @@ def _assert_val(self, value): def _check_val(value): return isinstance(value, BlockEntity) - def _dirty(self): - self._parent_chunk().changed = True - def __repr__(self) -> str: """ Return repr(self). """ super_repr = ( "".join(f"\n\t{key}:{val}" for key, val in self.data.items()) + "\n" ) - return f"BlockEntityDict({self._parent_chunk().cx},{self._parent_chunk().cz},{super_repr})" + return f"BlockEntityDict({super_repr})" def clear(self) -> None: """ Remove all items from list. """ - self._dirty() self.data.clear() def keys(self) -> Generator[Coordinate, None, None]: @@ -74,7 +68,6 @@ def copy(self) -> "BlockEntityDict": def insert(self, block_entity: BlockEntity) -> None: """ Insert block_entity at its coordinates. """ self._assert_val(block_entity) - self._dirty() self.data[block_entity.location] = block_entity def pop(self, coordinate: Coordinate) -> BlockEntity: @@ -85,14 +78,12 @@ def pop(self, coordinate: Coordinate) -> BlockEntity: """ self._assert_key(coordinate) if coordinate in self.data: - self._dirty() return self.data.pop(coordinate) raise IndexError def __delitem__(self, coordinate: Coordinate) -> None: """ Delete self[key]. """ self._assert_key(coordinate) - self._dirty() super().__delitem__(coordinate) def _check_block_entity(self, coordinate: Coordinate, block_entity: BlockEntity) -> BlockEntity: @@ -116,7 +107,6 @@ def popitem(self): raise NotImplementedError def update(self, block_entities: InputType) -> None: - self._dirty() for block_entity in block_entities: self._assert_val(block_entity) self.data[block_entity.location] = block_entity diff --git a/amulet/api/chunk/chunk.py b/amulet/api/chunk/chunk.py index e8c5e7b0a..64e7dee0a 100644 --- a/amulet/api/chunk/chunk.py +++ b/amulet/api/chunk/chunk.py @@ -32,7 +32,7 @@ def __init__(self, cx: int, cz: int): self.__biome_palette = BiomeManager() self._biomes = None self._entities = EntityList(self) - self._block_entities = BlockEntityDict(self) + self._block_entities = BlockEntityDict() self._status = Status(self) self.misc = {} # all entries that are not important enough to get an attribute @@ -282,7 +282,7 @@ def block_entities(self, value: BlockEntityDict.InputType): """ if self._block_entities != value: self.changed = True - self._block_entities = BlockEntityDict(self, value) + self._block_entities = BlockEntityDict(value) @property def status(self) -> Status: From 1149b3399773696362960379702327ece24959b1 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 11 Aug 2020 11:49:14 +0100 Subject: [PATCH 06/10] Removed the dirty logic from the ChunkArray --- amulet/api/chunk/chunk_array.py | 92 --------------------------------- 1 file changed, 92 deletions(-) diff --git a/amulet/api/chunk/chunk_array.py b/amulet/api/chunk/chunk_array.py index fb8a6b70a..1ee0de2a1 100644 --- a/amulet/api/chunk/chunk_array.py +++ b/amulet/api/chunk/chunk_array.py @@ -16,95 +16,3 @@ def __array_finalize__(self, obj): if obj is None: return self._parent_chunk = getattr(obj, "_parent_chunk", None) - - def _dirty(self): - self._parent_chunk().changed = True - - def byteswap(self, inplace=False): - if inplace: - self._dirty() - numpy.ndarray.byteswap(self, inplace) - - def fill(self, value): - self._dirty() - numpy.ndarray.fill(self, value) - - def itemset(self, *args): - self._dirty() - numpy.ndarray.itemset(*args) - - def partition(self, kth, axis=-1, kind="introselect", order=None): - self._dirty() - numpy.ndarray.partition(self, kth, axis, kind, order) - - def put(self, indices, values, mode="raise"): - self._dirty() - numpy.ndarray.put(self, indices, values, mode) - - def resize(self, *new_shape, refcheck=True): - self._dirty() - numpy.ndarray.resize(self, *new_shape, refcheck=refcheck) - - def sort(self, axis=-1, kind="quicksort", order=None): - self._dirty() - numpy.ndarray.sort(self, axis, kind, order) - - def squeeze(self, axis=None): - self._dirty() - numpy.ndarray.squeeze(self, axis) - - def __iadd__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__iadd__(self, *args, **kwargs) - - def __iand__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__iand__(self, *args, **kwargs) - - def __ifloordiv__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__ifloordiv__(self, *args, **kwargs) - - def __ilshift__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__ilshift__(self, *args, **kwargs) - - def __imatmul__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__imatmul__(self, *args, **kwargs) - - def __imod__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__imod__(self, *args, **kwargs) - - def __imul__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__imul__(self, *args, **kwargs) - - def __ior__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__ior__(self, *args, **kwargs) - - def __ipow__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__ipow__(self, *args, **kwargs) - - def __irshift__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__irshift__(self, *args, **kwargs) - - def __isub__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__isub__(self, *args, **kwargs) - - def __itruediv__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__itruediv__(self, *args, **kwargs) - - def __ixor__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__ixor__(self, *args, **kwargs) - - def __setitem__(self, *args, **kwargs): - self._dirty() - numpy.ndarray.__setitem__(self, *args, **kwargs) From bfddde1ae03547b613edf967737036483c345643 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 11 Aug 2020 11:49:53 +0100 Subject: [PATCH 07/10] fixed the name of BaseFormatWrapper --- amulet/api/wrapper/__init__.py | 2 +- amulet/api/wrapper/format_wrapper.py | 2 +- amulet/api/wrapper/structure_format_wrapper.py | 4 ++-- amulet/api/wrapper/world_format_wrapper.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/amulet/api/wrapper/__init__.py b/amulet/api/wrapper/__init__.py index 4dd376d27..b480d7801 100644 --- a/amulet/api/wrapper/__init__.py +++ b/amulet/api/wrapper/__init__.py @@ -1,4 +1,4 @@ -from amulet.api.wrapper.format_wrapper import BaseFormatWraper +from amulet.api.wrapper.format_wrapper import BaseFormatWrapper from amulet.api.wrapper.world_format_wrapper import WorldFormatWrapper from amulet.api.wrapper.structure_format_wrapper import StructureFormatWrapper from amulet.api.wrapper.chunk.translator import Translator diff --git a/amulet/api/wrapper/format_wrapper.py b/amulet/api/wrapper/format_wrapper.py index cc91ee275..85dcf6917 100644 --- a/amulet/api/wrapper/format_wrapper.py +++ b/amulet/api/wrapper/format_wrapper.py @@ -26,7 +26,7 @@ from amulet.api.wrapper.chunk.translator import Translator -class BaseFormatWraper: +class BaseFormatWrapper: """ The Format class is a class that sits between the serialised world or structure data and the program using amulet-core. The Format class is used to access chunks from the serialised source in the universal format and write them back again. diff --git a/amulet/api/wrapper/structure_format_wrapper.py b/amulet/api/wrapper/structure_format_wrapper.py index d7936eb6c..f18623c2f 100644 --- a/amulet/api/wrapper/structure_format_wrapper.py +++ b/amulet/api/wrapper/structure_format_wrapper.py @@ -1,8 +1,8 @@ -from .format_wrapper import BaseFormatWraper +from .format_wrapper import BaseFormatWrapper from amulet.api.data_types import PathOrBuffer -class StructureFormatWrapper(BaseFormatWraper): +class StructureFormatWrapper(BaseFormatWrapper): def __init__(self, path_or_buffer: PathOrBuffer): super().__init__() self._path_or_buffer = path_or_buffer diff --git a/amulet/api/wrapper/world_format_wrapper.py b/amulet/api/wrapper/world_format_wrapper.py index 626db7ea7..b5de5589c 100644 --- a/amulet/api/wrapper/world_format_wrapper.py +++ b/amulet/api/wrapper/world_format_wrapper.py @@ -4,7 +4,7 @@ from amulet import log, IMG_DIRECTORY from amulet.api.data_types import ChunkCoordinates -from .format_wrapper import BaseFormatWraper +from .format_wrapper import BaseFormatWrapper if TYPE_CHECKING: from amulet.api.chunk import Chunk @@ -15,7 +15,7 @@ ) -class WorldFormatWrapper(BaseFormatWraper): +class WorldFormatWrapper(BaseFormatWrapper): _missing_world_icon = missing_world_icon def __init__(self, world_path: str): From 097580d710d6045dd51de56195f58f341d4e608f Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 11 Aug 2020 11:58:53 +0100 Subject: [PATCH 08/10] Removed dirty logic from entity_list --- amulet/api/chunk/chunk.py | 4 +- amulet/api/chunk/entity_list.py | 95 ++++----------------------------- 2 files changed, 13 insertions(+), 86 deletions(-) diff --git a/amulet/api/chunk/chunk.py b/amulet/api/chunk/chunk.py index 64e7dee0a..edc056391 100644 --- a/amulet/api/chunk/chunk.py +++ b/amulet/api/chunk/chunk.py @@ -31,7 +31,7 @@ def __init__(self, cx: int, cz: int): self.__block_palette = BlockManager() self.__biome_palette = BiomeManager() self._biomes = None - self._entities = EntityList(self) + self._entities = EntityList() self._block_entities = BlockEntityDict() self._status = Status(self) self.misc = {} # all entries that are not important enough to get an attribute @@ -263,7 +263,7 @@ def entities(self, value: Iterable[Entity]): """ if self._entities != value: self.changed = True - self._entities = EntityList(self, value) + self._entities = EntityList(value) @property def block_entities(self) -> BlockEntityDict: diff --git a/amulet/api/chunk/entity_list.py b/amulet/api/chunk/entity_list.py index 4d713bd36..a155d53b3 100644 --- a/amulet/api/chunk/entity_list.py +++ b/amulet/api/chunk/entity_list.py @@ -1,17 +1,12 @@ from collections import UserList -from typing import TYPE_CHECKING, Iterable, Union, overload, Any, Generator +from typing import Iterable, Generator import copy from amulet.api.entity import Entity -import weakref - -if TYPE_CHECKING: - from amulet.api.chunk import Chunk class ChunkList(UserList): - def __init__(self, parent_chunk: "Chunk", iterable: Iterable = ()): + def __init__(self, iterable: Iterable = ()): super(ChunkList, self).__init__(list(iterable)) - self._parent_chunk = weakref.ref(parent_chunk) def _check_all_types(self, value): [self._check_type(val) for val in value] @@ -19,87 +14,24 @@ def _check_all_types(self, value): def _check_type(self, value): raise NotImplementedError - def _dirty(self): - self._parent_chunk().changed = True - def append(self, value) -> None: """ Append value to the end of the list. """ self._check_type(value) - self._dirty() super().append(value) - def clear(self) -> None: - """ Remove all items from list. """ - self._dirty() - super().clear() - def copy(self) -> "ChunkList": return copy.deepcopy(self) def extend(self, iterable: Iterable[Entity]) -> None: """ Extend list by appending elements from the iterable. """ self._check_all_types(iterable) - self._dirty() super().extend(iterable) def insert(self, index: int, value) -> None: """ Insert value before index. """ self._check_type(value) - self._dirty() super().insert(index, value) - def pop(self, index: int = -1) -> Any: - """ - Remove and return item at index (default last). - - Raises IndexError if list is empty or index is out of range. - """ - self._dirty() - return super().pop(index) - - def remove(self, value) -> None: - """ - Remove first occurrence of value. - - Raises ValueError if the value is not present. - """ - self._dirty() - super().remove(value) - - def reverse(self) -> None: - """ Reverse *IN PLACE*. """ - self._dirty() - super().reverse() - - def sort(self, *args, **kwargs): - """ Stable sort *IN PLACE*. """ - self._dirty() - super().sort(*args, **kwargs) - - def __delitem__(self, i: Union[int, slice]) -> None: - """ Delete self[key]. """ - self._dirty() - super().__delitem__(i) - - def __iadd__(self, x: "ChunkList") -> "ChunkList": - """ Implement self+=value. """ - self._dirty() - return super().__iadd__(x) - - def __imul__(self, n: int) -> "ChunkList": - """ Implement self*=value. """ - self._dirty() - return super().__imul__(n) - - @overload - def __setitem__(self, i: int, o) -> None: - ... - - def __setitem__(self, i: slice, o: Iterable) -> None: - """ Set self[key] to value. """ - self._dirty() - super().__setitem__(i, o) - def __repr__(self): if not self: return "[]" @@ -115,24 +47,19 @@ def __iter__(self) -> Generator[Entity, None, None]: def __repr__(self) -> str: """ Return repr(self). """ - return f"EntityList({self._parent_chunk().cx},{self._parent_chunk().cz},{super().__repr__()})" + return f"EntityList({super().__repr__()})" if __name__ == "__main__": import amulet_nbt - from amulet.api.chunk import Chunk - c = Chunk(0, 0) - print( - c.entities.__class__ - ) # this is - print(isinstance(c.entities, EntityList)) # but this is false. WHY??????? + entities = EntityList() block_ents = [ - Entity("minecraft", "creeper", 0, 0, 0, amulet_nbt.NBTFile()), - Entity("minecraft", "cow", 0, 0, 0, amulet_nbt.NBTFile()), - Entity("minecraft", "pig", 0, 0, 0, amulet_nbt.NBTFile()), - Entity("minecraft", "sheep", 0, 0, 0, amulet_nbt.NBTFile()), + Entity("minecraft", "creeper", 0.0, 0.0, 0.0, amulet_nbt.NBTFile()), + Entity("minecraft", "cow", 0.0, 0.0, 0.0, amulet_nbt.NBTFile()), + Entity("minecraft", "pig", 0.0, 0.0, 0.0, amulet_nbt.NBTFile()), + Entity("minecraft", "sheep", 0.0, 0.0, 0.0, amulet_nbt.NBTFile()), ] - c.entities.append(Entity("minecraft", "cow", 0, 0, 0, amulet_nbt.NBTFile())) - c.entities += block_ents - print(c.entities) + entities.append(Entity("minecraft", "cow", 0.0, 0.0, 0.0, amulet_nbt.NBTFile())) + entities += block_ents + print(entities) From ffb7f58a84cd114593c93a31b03e2531c8b4b060 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 11 Aug 2020 13:17:56 +0100 Subject: [PATCH 09/10] Removed some more of the automatic change logic --- amulet/api/chunk/chunk.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/amulet/api/chunk/chunk.py b/amulet/api/chunk/chunk.py index edc056391..22ff80860 100644 --- a/amulet/api/chunk/chunk.py +++ b/amulet/api/chunk/chunk.py @@ -202,7 +202,6 @@ def biomes(self, value: numpy.ndarray): numpy.issubdtype( value.dtype, numpy.integer ), "dtype must be an unsigned integer" - self.changed = True self._biomes = Biomes(self, value) @property @@ -262,7 +261,6 @@ def entities(self, value: Iterable[Entity]): :return: """ if self._entities != value: - self.changed = True self._entities = EntityList(value) @property @@ -281,7 +279,6 @@ def block_entities(self, value: BlockEntityDict.InputType): :return: """ if self._block_entities != value: - self.changed = True self._block_entities = BlockEntityDict(value) @property From cd706ecc7b8aa58c07409f0a3515cab1f1b5da40 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Tue, 11 Aug 2020 14:24:56 +0100 Subject: [PATCH 10/10] Reformatted --- amulet/api/chunk/block_entity_dict.py | 4 ++- amulet/api/structure.py | 4 ++- amulet/api/world.py | 51 ++++++++++++++++----------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/amulet/api/chunk/block_entity_dict.py b/amulet/api/chunk/block_entity_dict.py index 3bc54a244..af6300a92 100644 --- a/amulet/api/chunk/block_entity_dict.py +++ b/amulet/api/chunk/block_entity_dict.py @@ -86,7 +86,9 @@ def __delitem__(self, coordinate: Coordinate) -> None: self._assert_key(coordinate) super().__delitem__(coordinate) - def _check_block_entity(self, coordinate: Coordinate, block_entity: BlockEntity) -> BlockEntity: + def _check_block_entity( + self, coordinate: Coordinate, block_entity: BlockEntity + ) -> BlockEntity: self._assert_key(coordinate) self._assert_val(block_entity) if coordinate != block_entity.location: diff --git a/amulet/api/structure.py b/amulet/api/structure.py index 66225b769..856bc2a55 100644 --- a/amulet/api/structure.py +++ b/amulet/api/structure.py @@ -199,7 +199,9 @@ def get_moved_chunk_slices( chunk_box = self._chunk_box(cx, cz, destination_chunk_shape) dst_box = chunk_box.intersection(dst_full_box) src_box = SelectionBox(-offset + dst_box.min, -offset + dst_box.max) - src_slices = src_box.chunk_slice(chunk.cx, chunk.cz, self.sub_chunk_size) + src_slices = src_box.chunk_slice( + chunk.cx, chunk.cz, self.sub_chunk_size + ) dst_slices = dst_box.chunk_slice(cx, cz, self.sub_chunk_size) yield chunk, src_slices, src_box, (cx, cz), dst_slices, dst_box diff --git a/amulet/api/world.py b/amulet/api/world.py index f02542b8c..799ea06cf 100644 --- a/amulet/api/world.py +++ b/amulet/api/world.py @@ -17,7 +17,12 @@ from .chunk import Chunk from .selection import SelectionGroup, SelectionBox from .paths import get_temp_dir -from .data_types import OperationType, Dimension, DimensionCoordinates, VersionIdentifierType +from .data_types import ( + OperationType, + Dimension, + DimensionCoordinates, + VersionIdentifierType, +) from ..utils.world_utils import block_coords_to_chunk_coords if TYPE_CHECKING: @@ -333,12 +338,12 @@ def get_block(self, x: int, y: int, z: int, dimension: Dimension) -> Block: return self.get_chunk(cx, cz, dimension).get_block(offset_x, y, offset_z) def get_version_block( - self, - x: int, - y: int, - z: int, - dimension: Dimension, - version: VersionIdentifierType, + self, + x: int, + y: int, + z: int, + dimension: Dimension, + version: VersionIdentifierType, ) -> Tuple[Union[Block, Entity], Optional[BlockEntity]]: """ Get a block at the specified location and convert it to the format of the version specified @@ -357,21 +362,22 @@ def get_version_block( chunk = self.get_chunk(cx, cz, dimension) offset_x, offset_z = x - 16 * cx, z - 16 * cz - output, extra_output, _ = self.translation_manager.get_version(*version).block.from_universal( - chunk.get_block(offset_x, y, offset_z), - chunk.block_entities.get((x, y, z)) + output, extra_output, _ = self.translation_manager.get_version( + *version + ).block.from_universal( + chunk.get_block(offset_x, y, offset_z), chunk.block_entities.get((x, y, z)) ) return output, extra_output def set_version_block( - self, - x: int, - y: int, - z: int, - dimension: Dimension, - version: VersionIdentifierType, - block: Block, - block_entity: BlockEntity + self, + x: int, + y: int, + z: int, + dimension: Dimension, + version: VersionIdentifierType, + block: Block, + block_entity: BlockEntity, ): """ Convert the block and block_entity from the given version format to the universal format and set at the location @@ -393,9 +399,12 @@ def set_version_block( chunk = self.create_chunk(cx, cz, dimension) offset_x, offset_z = x - 16 * cx, z - 16 * cz - universal_block, universal_block_entity, _ = self.translation_manager.get_version(*version).block.to_universal( - block, - block_entity + ( + universal_block, + universal_block_entity, + _, + ) = self.translation_manager.get_version(*version).block.to_universal( + block, block_entity ) chunk.set_block(offset_x, y, offset_z, block), chunk.block_entities[(x, y, z)] = block_entity