amulet-core 2.0a8__cp312-cp312-win_amd64.whl → 2.0.1.0.1297307203.19.43.34808.0a0__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of amulet-core might be problematic. Click here for more details.
- amulet/core/__init__.py +36 -0
- amulet/core/__pyinstaller/hook-amulet.core.py +4 -0
- amulet/core/_amulet_core.cp312-win_amd64.pyd +0 -0
- amulet/core/_amulet_core.pyi +7 -0
- amulet/{_version.py → core/_version.py} +3 -3
- amulet/core/amulet_core.dll +0 -0
- amulet/core/amulet_core.lib +0 -0
- amulet/core/amulet_coreConfig.cmake +18 -0
- amulet/{biome.pyi → core/biome/__init__.pyi} +3 -3
- amulet/core/biome/biome.hpp +53 -0
- amulet/{block.pyi → core/block/__init__.pyi} +25 -26
- amulet/core/block/block.hpp +156 -0
- amulet/{block_entity.pyi → core/block_entity/__init__.pyi} +7 -7
- amulet/core/block_entity/block_entity.hpp +84 -0
- amulet/{errors.py → core/chunk/__init__.pyi} +37 -33
- amulet/core/chunk/chunk.hpp +126 -0
- amulet/core/chunk/component/__init__.pyi +18 -0
- amulet/core/chunk/component/biome_3d_component.hpp +96 -0
- amulet/core/chunk/component/block_component.hpp +101 -0
- amulet/core/chunk/component/block_component.pyi +28 -0
- amulet/core/chunk/component/block_entity_component.hpp +119 -0
- amulet/core/chunk/component/section_array_map.hpp +129 -0
- amulet/{chunk_components.pyi → core/chunk/component/section_array_map.pyi} +4 -24
- amulet/core/dll.hpp +21 -0
- amulet/core/entity/__init__.pyi +105 -0
- amulet/core/entity/entity.hpp +100 -0
- amulet/{palette → core/palette}/__init__.pyi +2 -2
- amulet/core/palette/biome_palette.hpp +65 -0
- amulet/{palette → core/palette}/biome_palette.pyi +8 -8
- amulet/core/palette/block_palette.hpp +71 -0
- amulet/{palette → core/palette}/block_palette.pyi +12 -10
- amulet/core/selection/__init__.pyi +8 -0
- amulet/core/selection/box.hpp +86 -0
- amulet/core/selection/box.pyi +215 -0
- amulet/core/selection/group.hpp +80 -0
- amulet/core/selection/group.pyi +213 -0
- amulet/{version.pyi → core/version/__init__.pyi} +58 -10
- amulet/core/version/version.hpp +204 -0
- {amulet_core-2.0a8.dist-info → amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info}/METADATA +25 -20
- amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info/RECORD +45 -0
- {amulet_core-2.0a8.dist-info → amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info}/WHEEL +1 -1
- amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info/entry_points.txt +2 -0
- amulet/__init__.cp312-win_amd64.pyd +0 -0
- amulet/__init__.py.cpp +0 -45
- amulet/__init__.pyi +0 -30
- amulet/__pyinstaller/hook-amulet.py +0 -4
- amulet/_init.py +0 -26
- amulet/biome.cpp +0 -36
- amulet/biome.hpp +0 -43
- amulet/biome.py.cpp +0 -122
- amulet/block.cpp +0 -435
- amulet/block.hpp +0 -119
- amulet/block.py.cpp +0 -377
- amulet/block_entity.cpp +0 -12
- amulet/block_entity.hpp +0 -56
- amulet/block_entity.py.cpp +0 -115
- amulet/chunk.cpp +0 -16
- amulet/chunk.hpp +0 -100
- amulet/chunk.py.cpp +0 -80
- amulet/chunk.pyi +0 -28
- amulet/chunk_components/biome_3d_component.cpp +0 -5
- amulet/chunk_components/biome_3d_component.hpp +0 -79
- amulet/chunk_components/block_component.cpp +0 -41
- amulet/chunk_components/block_component.hpp +0 -88
- amulet/chunk_components/block_entity_component.cpp +0 -5
- amulet/chunk_components/block_entity_component.hpp +0 -147
- amulet/chunk_components/section_array_map.cpp +0 -129
- amulet/chunk_components/section_array_map.hpp +0 -147
- amulet/collections/eq.py.hpp +0 -37
- amulet/collections/hash.py.hpp +0 -27
- amulet/collections/holder.py.hpp +0 -37
- amulet/collections/iterator.py.hpp +0 -80
- amulet/collections/mapping.py.hpp +0 -199
- amulet/collections/mutable_mapping.py.hpp +0 -226
- amulet/collections/sequence.py.hpp +0 -163
- amulet/collections.pyi +0 -40
- amulet/data_types.py +0 -29
- amulet/entity.py +0 -182
- amulet/game/__init__.py +0 -7
- amulet/game/_game.py +0 -152
- amulet/game/_universal/__init__.py +0 -1
- amulet/game/_universal/_biome.py +0 -17
- amulet/game/_universal/_block.py +0 -47
- amulet/game/_universal/_version.py +0 -68
- amulet/game/abc/__init__.py +0 -22
- amulet/game/abc/_block_specification.py +0 -150
- amulet/game/abc/biome.py +0 -213
- amulet/game/abc/block.py +0 -331
- amulet/game/abc/game_version_container.py +0 -25
- amulet/game/abc/json_interface.py +0 -27
- amulet/game/abc/version.py +0 -44
- amulet/game/bedrock/__init__.py +0 -1
- amulet/game/bedrock/_biome.py +0 -35
- amulet/game/bedrock/_block.py +0 -42
- amulet/game/bedrock/_version.py +0 -165
- amulet/game/java/__init__.py +0 -2
- amulet/game/java/_biome.py +0 -35
- amulet/game/java/_block.py +0 -60
- amulet/game/java/_version.py +0 -176
- amulet/game/translate/__init__.py +0 -12
- amulet/game/translate/_functions/__init__.py +0 -15
- amulet/game/translate/_functions/_code_functions/__init__.py +0 -0
- amulet/game/translate/_functions/_code_functions/_text.py +0 -553
- amulet/game/translate/_functions/_code_functions/banner_pattern.py +0 -67
- amulet/game/translate/_functions/_code_functions/bedrock_chest_connection.py +0 -152
- amulet/game/translate/_functions/_code_functions/bedrock_moving_block_pos.py +0 -88
- amulet/game/translate/_functions/_code_functions/bedrock_sign.py +0 -152
- amulet/game/translate/_functions/_code_functions/bedrock_skull_rotation.py +0 -16
- amulet/game/translate/_functions/_code_functions/custom_name.py +0 -146
- amulet/game/translate/_functions/_frozen.py +0 -66
- amulet/game/translate/_functions/_state.py +0 -54
- amulet/game/translate/_functions/_typing.py +0 -98
- amulet/game/translate/_functions/abc.py +0 -123
- amulet/game/translate/_functions/carry_nbt.py +0 -160
- amulet/game/translate/_functions/carry_properties.py +0 -80
- amulet/game/translate/_functions/code.py +0 -143
- amulet/game/translate/_functions/map_block_name.py +0 -66
- amulet/game/translate/_functions/map_nbt.py +0 -111
- amulet/game/translate/_functions/map_properties.py +0 -93
- amulet/game/translate/_functions/multiblock.py +0 -112
- amulet/game/translate/_functions/new_block.py +0 -42
- amulet/game/translate/_functions/new_entity.py +0 -43
- amulet/game/translate/_functions/new_nbt.py +0 -206
- amulet/game/translate/_functions/new_properties.py +0 -64
- amulet/game/translate/_functions/sequence.py +0 -51
- amulet/game/translate/_functions/walk_input_nbt.py +0 -331
- amulet/game/translate/_translator.py +0 -433
- amulet/img/__init__.py +0 -10
- amulet/img/missing_no.png +0 -0
- amulet/img/missing_pack.png +0 -0
- amulet/img/missing_world.png +0 -0
- amulet/io/binary_reader.hpp +0 -45
- amulet/io/binary_writer.hpp +0 -30
- amulet/item.py +0 -75
- amulet/level/__init__.pyi +0 -23
- amulet/level/_load.py +0 -100
- amulet/level/abc/__init__.py +0 -12
- amulet/level/abc/_chunk_handle.py +0 -358
- amulet/level/abc/_dimension.py +0 -86
- amulet/level/abc/_history/__init__.py +0 -1
- amulet/level/abc/_history/_cache.py +0 -224
- amulet/level/abc/_history/_history_manager.py +0 -291
- amulet/level/abc/_level/__init__.py +0 -5
- amulet/level/abc/_level/_compactable_level.py +0 -10
- amulet/level/abc/_level/_creatable_level.py +0 -28
- amulet/level/abc/_level/_disk_level.py +0 -17
- amulet/level/abc/_level/_level.py +0 -449
- amulet/level/abc/_level/_loadable_level.py +0 -42
- amulet/level/abc/_player_storage.py +0 -7
- amulet/level/abc/_raw_level.py +0 -187
- amulet/level/abc/_registry.py +0 -40
- amulet/level/java/__init__.pyi +0 -16
- amulet/level/java/_chunk_handle.py +0 -17
- amulet/level/java/_dimension.py +0 -20
- amulet/level/java/_level.py +0 -184
- amulet/level/java/_raw/__init__.pyi +0 -15
- amulet/level/java/_raw/_chunk.pyi +0 -23
- amulet/level/java/_raw/_constant.py +0 -9
- amulet/level/java/_raw/_data_pack/__init__.py +0 -2
- amulet/level/java/_raw/_data_pack/data_pack.py +0 -241
- amulet/level/java/_raw/_data_pack/data_pack_manager.py +0 -77
- amulet/level/java/_raw/_dimension.py +0 -86
- amulet/level/java/_raw/_level.py +0 -507
- amulet/level/java/_raw/_typing.py +0 -3
- amulet/level/java/_raw/java_chunk_decode.cpp +0 -531
- amulet/level/java/_raw/java_chunk_decode.hpp +0 -23
- amulet/level/java/_raw/java_chunk_encode.cpp +0 -25
- amulet/level/java/_raw/java_chunk_encode.hpp +0 -23
- amulet/level/java/anvil/__init__.py +0 -2
- amulet/level/java/anvil/_dimension.py +0 -170
- amulet/level/java/anvil/_region.py +0 -421
- amulet/level/java/anvil/_sector_manager.py +0 -223
- amulet/level/java/chunk.pyi +0 -81
- amulet/level/java/chunk_/_chunk.py +0 -260
- amulet/level/java/chunk_/components/inhabited_time.py +0 -12
- amulet/level/java/chunk_/components/last_update.py +0 -12
- amulet/level/java/chunk_/components/legacy_version.py +0 -12
- amulet/level/java/chunk_/components/light_populated.py +0 -12
- amulet/level/java/chunk_/components/named_height_2d.py +0 -37
- amulet/level/java/chunk_/components/status.py +0 -11
- amulet/level/java/chunk_/components/terrain_populated.py +0 -12
- amulet/level/java/chunk_components/data_version_component.cpp +0 -32
- amulet/level/java/chunk_components/data_version_component.hpp +0 -31
- amulet/level/java/chunk_components/java_raw_chunk_component.cpp +0 -56
- amulet/level/java/chunk_components/java_raw_chunk_component.hpp +0 -45
- amulet/level/java/chunk_components.pyi +0 -22
- amulet/level/java/java_chunk.cpp +0 -170
- amulet/level/java/java_chunk.hpp +0 -141
- amulet/level/java/long_array.hpp +0 -175
- amulet/level/java/long_array.pyi +0 -39
- amulet/level/temporary_level/__init__.py +0 -1
- amulet/level/temporary_level/_level.py +0 -16
- amulet/mesh/__init__.py +0 -0
- amulet/mesh/block/__init__.pyi +0 -301
- amulet/mesh/block/_cube.py +0 -198
- amulet/mesh/block/_missing_block.py +0 -20
- amulet/mesh/block/block_mesh.cpp +0 -107
- amulet/mesh/block/block_mesh.hpp +0 -207
- amulet/mesh/util.py +0 -17
- amulet/palette/biome_palette.hpp +0 -85
- amulet/palette/block_palette.cpp +0 -32
- amulet/palette/block_palette.hpp +0 -93
- amulet/player.py +0 -62
- amulet/pybind11/collections.hpp +0 -118
- amulet/pybind11/numpy.hpp +0 -26
- amulet/pybind11/py_module.hpp +0 -34
- amulet/pybind11/type_hints.hpp +0 -51
- amulet/pybind11/types.hpp +0 -25
- amulet/pybind11/typing.hpp +0 -7
- amulet/resource_pack/__init__.py +0 -63
- amulet/resource_pack/abc/__init__.py +0 -2
- amulet/resource_pack/abc/resource_pack.py +0 -38
- amulet/resource_pack/abc/resource_pack_manager.py +0 -85
- amulet/resource_pack/java/__init__.py +0 -2
- amulet/resource_pack/java/download_resources.py +0 -212
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_black.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_blue.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_brown.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_cyan.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_gray.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_green.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_light_blue.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_light_gray.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_lime.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_magenta.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_orange.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_pink.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_purple.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_red.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_white.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_yellow.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/barrier.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/end_portal.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/grass.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/lava.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/structure_void.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/water.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/pack.png +0 -0
- amulet/resource_pack/java/resource_pack.py +0 -44
- amulet/resource_pack/java/resource_pack_manager.py +0 -563
- amulet/resource_pack/unknown_resource_pack.py +0 -10
- amulet/selection/__init__.py +0 -2
- amulet/selection/abstract_selection.py +0 -342
- amulet/selection/box.py +0 -852
- amulet/selection/group.py +0 -481
- amulet/utils/__init__.pyi +0 -23
- amulet/utils/call_spec/__init__.py +0 -24
- amulet/utils/call_spec/_call_spec.py +0 -257
- amulet/utils/cast.py +0 -10
- amulet/utils/comment_json.py +0 -188
- amulet/utils/format_utils.py +0 -41
- amulet/utils/generator.py +0 -18
- amulet/utils/matrix.py +0 -243
- amulet/utils/numpy.hpp +0 -36
- amulet/utils/numpy.pyi +0 -11
- amulet/utils/numpy_helpers.py +0 -19
- amulet/utils/shareable_lock.py +0 -335
- amulet/utils/signal/__init__.py +0 -10
- amulet/utils/signal/_signal.py +0 -228
- amulet/utils/task_manager.py +0 -235
- amulet/utils/typed_property.py +0 -111
- amulet/utils/weakref.py +0 -70
- amulet/utils/world_utils.py +0 -102
- amulet/version.cpp +0 -136
- amulet/version.hpp +0 -142
- amulet/version.py.cpp +0 -281
- amulet_core-2.0a8.dist-info/RECORD +0 -241
- amulet_core-2.0a8.dist-info/entry_points.txt +0 -2
- /amulet/{__pyinstaller → core/__pyinstaller}/__init__.py +0 -0
- /amulet/{py.typed → core/py.typed} +0 -0
- {amulet_core-2.0a8.dist-info → amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info}/top_level.txt +0 -0
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import shutil
|
|
4
|
-
from typing import Optional, Callable, cast, IO, Self
|
|
5
|
-
from threading import Lock
|
|
6
|
-
import os
|
|
7
|
-
from weakref import ref, finalize
|
|
8
|
-
import time
|
|
9
|
-
import glob
|
|
10
|
-
import re
|
|
11
|
-
import tempfile
|
|
12
|
-
|
|
13
|
-
import portalocker
|
|
14
|
-
from leveldb import LevelDB
|
|
15
|
-
from amulet.utils.weakref import CallableWeakMethod
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
TempPattern = re.compile(r"amulettmp.*?-(?P<time>\d+)")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _temp_dir() -> str:
|
|
22
|
-
temp_dir = os.environ.get("CACHE_DIR")
|
|
23
|
-
if temp_dir is None:
|
|
24
|
-
raise RuntimeError
|
|
25
|
-
return temp_dir
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _clear_temp_dirs() -> None:
|
|
29
|
-
"""
|
|
30
|
-
Try and delete historic temporary directories.
|
|
31
|
-
If things went very wrong in past sessions temporary directories may still exist.
|
|
32
|
-
"""
|
|
33
|
-
for path in glob.glob(
|
|
34
|
-
os.path.join(glob.escape(tempfile.gettempdir()), "amulettmp*")
|
|
35
|
-
) + glob.glob(
|
|
36
|
-
os.path.join(glob.escape(_temp_dir()), "**", "amulettmp*"), recursive=True
|
|
37
|
-
):
|
|
38
|
-
name = os.path.basename(path)
|
|
39
|
-
match = TempPattern.fullmatch(name)
|
|
40
|
-
if match and int(match.group("time")) < (time.time() - 7 * 24 * 3600):
|
|
41
|
-
lock_path = os.path.join(path, "lock")
|
|
42
|
-
if os.path.exists(lock_path):
|
|
43
|
-
with open(lock_path) as lock:
|
|
44
|
-
# make sure it is not locked by another process
|
|
45
|
-
try:
|
|
46
|
-
portalocker.lock(lock, portalocker.LockFlags.EXCLUSIVE)
|
|
47
|
-
except:
|
|
48
|
-
continue
|
|
49
|
-
else:
|
|
50
|
-
portalocker.unlock(lock)
|
|
51
|
-
shutil.rmtree(path, ignore_errors=True)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
_clear_temp_dirs()
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class TempDir(str):
|
|
58
|
-
"""
|
|
59
|
-
A temporary directory to do with as you wish.
|
|
60
|
-
|
|
61
|
-
>>> t = TempDir()
|
|
62
|
-
>>> path = os.path.join(t, "your_file.txt") # TempDir is a subclass of str
|
|
63
|
-
>>> # make sure all files in the temporary directory are closed before releasing or closing this object.
|
|
64
|
-
>>> # The temporary directory will be deleted when the last reference to `t` is lost or when `t.close()` is called
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
__lock: IO | None
|
|
68
|
-
__finalise: finalize
|
|
69
|
-
|
|
70
|
-
def __new__(cls, group: str) -> Self:
|
|
71
|
-
cache_dir = os.path.join(_temp_dir(), group)
|
|
72
|
-
os.makedirs(cache_dir, exist_ok=True)
|
|
73
|
-
return super().__new__(
|
|
74
|
-
cls,
|
|
75
|
-
tempfile.mkdtemp(
|
|
76
|
-
prefix="amulettmp",
|
|
77
|
-
suffix=f"-{time.time():.0f}",
|
|
78
|
-
dir=cache_dir,
|
|
79
|
-
),
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
def __init__(self, group: str) -> None:
|
|
83
|
-
self.__lock = open(os.path.join(self, "lock"), "w")
|
|
84
|
-
portalocker.lock(self.__lock, portalocker.LockFlags.EXCLUSIVE)
|
|
85
|
-
self.__finalise = finalize(self, CallableWeakMethod(self._close))
|
|
86
|
-
|
|
87
|
-
def _close(self) -> None:
|
|
88
|
-
if self.__lock is not None:
|
|
89
|
-
portalocker.unlock(self.__lock)
|
|
90
|
-
self.__lock.close()
|
|
91
|
-
self.__lock = None
|
|
92
|
-
shutil.rmtree(self)
|
|
93
|
-
|
|
94
|
-
def close(self) -> None:
|
|
95
|
-
"""Close the lock and delete the directory."""
|
|
96
|
-
self.__finalise()
|
|
97
|
-
|
|
98
|
-
def __del__(self) -> None:
|
|
99
|
-
self.__finalise()
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class DiskCache:
|
|
103
|
-
"""
|
|
104
|
-
A key, value database with a fast access RAM component and a longer term storage disk component.
|
|
105
|
-
Keys and values are both bytes.
|
|
106
|
-
The disk component is a leveldb database.
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
|
-
def __init__(self, path: str, max_size: int) -> None:
|
|
110
|
-
"""
|
|
111
|
-
Create a new DiskCache
|
|
112
|
-
:param path: The path to save the disk component to.
|
|
113
|
-
:param max_size: The maximum amount of RAM that values can occupy. Key size is assumed negligible.
|
|
114
|
-
When this is overflowed, the least recently used entries are unloaded to the disk storage.
|
|
115
|
-
"""
|
|
116
|
-
self._lock = Lock()
|
|
117
|
-
self._ram: dict[bytes, tuple[bytes, bool]] = {}
|
|
118
|
-
self._path = path
|
|
119
|
-
self._disk = LevelDB(path, create_if_missing=True)
|
|
120
|
-
self._max_size: int = max_size
|
|
121
|
-
self._size: int = 0
|
|
122
|
-
self.__finalise = finalize(self, CallableWeakMethod(self._close))
|
|
123
|
-
|
|
124
|
-
def _close(self) -> None:
|
|
125
|
-
self._disk.close()
|
|
126
|
-
shutil.rmtree(self._path, ignore_errors=True)
|
|
127
|
-
|
|
128
|
-
def __del__(self) -> None:
|
|
129
|
-
self.__finalise()
|
|
130
|
-
|
|
131
|
-
@property
|
|
132
|
-
def max_size(self) -> int:
|
|
133
|
-
return self._max_size
|
|
134
|
-
|
|
135
|
-
@max_size.setter
|
|
136
|
-
def max_size(self, max_size: int) -> None:
|
|
137
|
-
if not isinstance(max_size, int):
|
|
138
|
-
raise TypeError
|
|
139
|
-
with self._lock:
|
|
140
|
-
self._max_size = max_size
|
|
141
|
-
self._free()
|
|
142
|
-
|
|
143
|
-
def __setitem__(self, key: bytes, value: bytes) -> None:
|
|
144
|
-
with self._lock:
|
|
145
|
-
self._remove(key)
|
|
146
|
-
self._ram[key] = (value, True)
|
|
147
|
-
self._size += len(value)
|
|
148
|
-
self._free()
|
|
149
|
-
|
|
150
|
-
def _remove(self, key: bytes) -> None:
|
|
151
|
-
if key in self._ram:
|
|
152
|
-
data = self._ram.pop(key)[0]
|
|
153
|
-
self._size -= len(data)
|
|
154
|
-
|
|
155
|
-
def __delitem__(self, key: bytes) -> None:
|
|
156
|
-
with self._lock:
|
|
157
|
-
self._remove(key)
|
|
158
|
-
if key in self._disk:
|
|
159
|
-
del self._disk[key]
|
|
160
|
-
|
|
161
|
-
def _free(self) -> None:
|
|
162
|
-
"""Push some values to disk"""
|
|
163
|
-
if self._size > self._max_size:
|
|
164
|
-
keys = iter(self._ram.copy())
|
|
165
|
-
while self._size > self._max_size:
|
|
166
|
-
key = next(keys)
|
|
167
|
-
value, changed = self._ram.pop(key)
|
|
168
|
-
self._size -= len(value)
|
|
169
|
-
if changed:
|
|
170
|
-
self._disk[key] = value
|
|
171
|
-
|
|
172
|
-
def __getitem__(self, key: bytes) -> bytes:
|
|
173
|
-
with self._lock:
|
|
174
|
-
if key in self._ram:
|
|
175
|
-
value = self._ram.pop(key)
|
|
176
|
-
# Push it to the end
|
|
177
|
-
self._ram[key] = value
|
|
178
|
-
return value[0]
|
|
179
|
-
elif key in self._disk:
|
|
180
|
-
data = self._disk[key]
|
|
181
|
-
self._ram[key] = (data, False)
|
|
182
|
-
self._size += len(data)
|
|
183
|
-
self._free()
|
|
184
|
-
return data
|
|
185
|
-
else:
|
|
186
|
-
raise KeyError
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
class GlobalDiskCache(DiskCache):
|
|
190
|
-
_instance_ref: Callable[[], Optional[GlobalDiskCache]] = cast(
|
|
191
|
-
Callable[[], Optional["GlobalDiskCache"]], lambda: None
|
|
192
|
-
)
|
|
193
|
-
_cache_size = 100_000_000
|
|
194
|
-
|
|
195
|
-
@classmethod
|
|
196
|
-
def instance(cls) -> GlobalDiskCache:
|
|
197
|
-
"""
|
|
198
|
-
Get the global disk cache instance.
|
|
199
|
-
The caller must store a strong reference to the returned value otherwise it will be destroyed.
|
|
200
|
-
"""
|
|
201
|
-
instance: Optional[GlobalDiskCache] = cls._instance_ref()
|
|
202
|
-
if instance is None:
|
|
203
|
-
instance = GlobalDiskCache()
|
|
204
|
-
cls._instance_ref = ref(instance)
|
|
205
|
-
return instance
|
|
206
|
-
|
|
207
|
-
@classmethod
|
|
208
|
-
def cache_size(cls) -> int:
|
|
209
|
-
instance: Optional[GlobalDiskCache] = cls._instance_ref()
|
|
210
|
-
if instance is None:
|
|
211
|
-
return cls._cache_size
|
|
212
|
-
else:
|
|
213
|
-
return instance.max_size
|
|
214
|
-
|
|
215
|
-
@classmethod
|
|
216
|
-
def set_cache_size(cls, size: int) -> None:
|
|
217
|
-
instance: Optional[GlobalDiskCache] = cls._instance_ref()
|
|
218
|
-
cls._cache_size = size
|
|
219
|
-
if instance is not None:
|
|
220
|
-
instance.max_size = size
|
|
221
|
-
|
|
222
|
-
def __init__(self) -> None:
|
|
223
|
-
self._temp_dir = TempDir("level_data")
|
|
224
|
-
super().__init__(os.path.join(self._temp_dir, "history_db"), 100_000_000)
|
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from uuid import uuid4
|
|
4
|
-
from threading import Lock
|
|
5
|
-
from typing import Sequence, Protocol, TypeVar, Generic, Mapping, Any
|
|
6
|
-
from weakref import WeakSet, WeakValueDictionary
|
|
7
|
-
from collections.abc import MutableMapping
|
|
8
|
-
|
|
9
|
-
from amulet.utils.signal import Signal, SignalInstanceCacheName
|
|
10
|
-
|
|
11
|
-
from ._cache import GlobalDiskCache
|
|
12
|
-
|
|
13
|
-
# TODO: consider adding a max undo option
|
|
14
|
-
# TODO: if we clear old undo info we should remove that data from the cache
|
|
15
|
-
# TODO: compact the cache periodically
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ResourceId(Protocol):
|
|
19
|
-
def __hash__(self) -> int:
|
|
20
|
-
"""A constant hash"""
|
|
21
|
-
...
|
|
22
|
-
|
|
23
|
-
def __eq__(self, other: Any) -> bool: ...
|
|
24
|
-
|
|
25
|
-
def __bytes__(self) -> bytes:
|
|
26
|
-
"""A constant bytes representation"""
|
|
27
|
-
...
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
ResourceIdT = TypeVar("ResourceIdT", bound=ResourceId)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class Resource:
|
|
34
|
-
__slots__ = ("index", "saved_index", "global_index", "exists")
|
|
35
|
-
|
|
36
|
-
def __init__(self) -> None:
|
|
37
|
-
# The index of the currently active revision
|
|
38
|
-
self.index: int = 0
|
|
39
|
-
# The index of the saved revision. -1 if the index no longer exists (overwritten or destroyed future)
|
|
40
|
-
self.saved_index: int = 0
|
|
41
|
-
# The global history index
|
|
42
|
-
self.global_index: int = 0
|
|
43
|
-
# Does the resource data exist
|
|
44
|
-
self.exists: list[bool] = [True]
|
|
45
|
-
|
|
46
|
-
def get_resource_key(self, uuid: bytes, resource_id: ResourceId) -> bytes:
|
|
47
|
-
return b"/".join((uuid, bytes(resource_id), str(self.index).encode()))
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class HistoryManagerPrivate:
|
|
51
|
-
lock: Lock
|
|
52
|
-
resources: WeakValueDictionary[bytes, MutableMapping[ResourceId, Resource]]
|
|
53
|
-
history: list[WeakSet[Resource]]
|
|
54
|
-
history_index: int
|
|
55
|
-
has_redo: bool
|
|
56
|
-
cache: GlobalDiskCache
|
|
57
|
-
|
|
58
|
-
def __init__(self) -> None:
|
|
59
|
-
self.lock = Lock()
|
|
60
|
-
self.resources = WeakValueDictionary()
|
|
61
|
-
self.history = [WeakSet()]
|
|
62
|
-
self.history_index = 0
|
|
63
|
-
self.has_redo = False
|
|
64
|
-
self.cache = GlobalDiskCache.instance()
|
|
65
|
-
|
|
66
|
-
def invalidate_future(self) -> None:
|
|
67
|
-
"""Destroy all future redo bins. Caller must acquire the lock"""
|
|
68
|
-
if self.has_redo:
|
|
69
|
-
self.has_redo = False
|
|
70
|
-
del self.history[self.history_index + 1 :]
|
|
71
|
-
for layer in self.resources.values():
|
|
72
|
-
for resource in layer.values():
|
|
73
|
-
if resource.saved_index > resource.index:
|
|
74
|
-
# A future index is saved that just got invalidated
|
|
75
|
-
resource.saved_index = -1
|
|
76
|
-
|
|
77
|
-
def reset(self) -> None:
|
|
78
|
-
with self.lock:
|
|
79
|
-
for layer in self.resources.values():
|
|
80
|
-
layer.clear()
|
|
81
|
-
self.history = [WeakSet()]
|
|
82
|
-
self.history_index = 0
|
|
83
|
-
self.has_redo = False
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class CustomDict(dict):
|
|
87
|
-
pass
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class HistoryManager:
|
|
91
|
-
__slots__ = (SignalInstanceCacheName, "_h")
|
|
92
|
-
|
|
93
|
-
_h: HistoryManagerPrivate
|
|
94
|
-
|
|
95
|
-
def __init__(self) -> None:
|
|
96
|
-
self._h = HistoryManagerPrivate()
|
|
97
|
-
|
|
98
|
-
def new_layer(self) -> HistoryManagerLayer:
|
|
99
|
-
uuid = uuid4().bytes
|
|
100
|
-
resources: MutableMapping[ResourceId, Resource] = CustomDict()
|
|
101
|
-
self._h.resources[uuid] = resources
|
|
102
|
-
return HistoryManagerLayer(self._h, uuid, resources)
|
|
103
|
-
|
|
104
|
-
history_changed = Signal[()]()
|
|
105
|
-
|
|
106
|
-
def create_undo_bin(self) -> None:
|
|
107
|
-
"""
|
|
108
|
-
Call this to create a new undo bin.
|
|
109
|
-
All changes made after this point will be part of the same undo bin until this is called again.
|
|
110
|
-
If this is not called, all changes will be part of the previous undo bin.
|
|
111
|
-
"""
|
|
112
|
-
with self._h.lock:
|
|
113
|
-
self._h.invalidate_future()
|
|
114
|
-
self._h.history_index += 1
|
|
115
|
-
self._h.history.append(WeakSet())
|
|
116
|
-
|
|
117
|
-
def mark_saved(self) -> None:
|
|
118
|
-
with self._h.lock:
|
|
119
|
-
for layer in self._h.resources.values():
|
|
120
|
-
for resource in layer.values():
|
|
121
|
-
resource.saved_index = resource.index
|
|
122
|
-
|
|
123
|
-
@property
|
|
124
|
-
def undo_count(self) -> int:
|
|
125
|
-
"""The number of times undo can be called."""
|
|
126
|
-
with self._h.lock:
|
|
127
|
-
return self._h.history_index
|
|
128
|
-
|
|
129
|
-
def undo(self) -> None:
|
|
130
|
-
"""Undo the changes in the current undo bin."""
|
|
131
|
-
with self._h.lock:
|
|
132
|
-
if self._h.history_index <= 0:
|
|
133
|
-
raise RuntimeError
|
|
134
|
-
for resource in self._h.history[self._h.history_index]:
|
|
135
|
-
resource.index -= 1
|
|
136
|
-
self._h.history_index -= 1
|
|
137
|
-
self._h.has_redo = True
|
|
138
|
-
|
|
139
|
-
def _redo_count(self) -> int:
|
|
140
|
-
return len(self._h.history) - (self._h.history_index + 1)
|
|
141
|
-
|
|
142
|
-
@property
|
|
143
|
-
def redo_count(self) -> int:
|
|
144
|
-
"""The number of times redo can be called."""
|
|
145
|
-
with self._h.lock:
|
|
146
|
-
return self._redo_count()
|
|
147
|
-
|
|
148
|
-
def redo(self) -> None:
|
|
149
|
-
"""Redo the changes in the next undo bin."""
|
|
150
|
-
with self._h.lock:
|
|
151
|
-
if not self._h.has_redo:
|
|
152
|
-
raise RuntimeError
|
|
153
|
-
self._h.history_index += 1
|
|
154
|
-
for resource in self._h.history[self._h.history_index]:
|
|
155
|
-
resource.index += 1
|
|
156
|
-
self._h.has_redo = bool(self._redo_count())
|
|
157
|
-
|
|
158
|
-
def reset(self) -> None:
|
|
159
|
-
"""Reset to the factory state."""
|
|
160
|
-
self._h.reset()
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class HistoryManagerLayer(Generic[ResourceIdT]):
|
|
164
|
-
__slots__ = ("_h", "_uuid", "_resources")
|
|
165
|
-
|
|
166
|
-
_h: HistoryManagerPrivate
|
|
167
|
-
_uuid: bytes
|
|
168
|
-
_resources: MutableMapping[ResourceIdT, Resource]
|
|
169
|
-
|
|
170
|
-
def __init__(
|
|
171
|
-
self,
|
|
172
|
-
_h: HistoryManagerPrivate,
|
|
173
|
-
uuid: bytes,
|
|
174
|
-
resources: MutableMapping[ResourceIdT, Resource],
|
|
175
|
-
) -> None:
|
|
176
|
-
"""This must not be used directly."""
|
|
177
|
-
self._h = _h
|
|
178
|
-
self._uuid = uuid
|
|
179
|
-
self._resources = resources
|
|
180
|
-
|
|
181
|
-
def resources(self) -> Sequence[ResourceIdT]:
|
|
182
|
-
"""
|
|
183
|
-
Get all resource ids from this layer.
|
|
184
|
-
:return:
|
|
185
|
-
"""
|
|
186
|
-
with self._h.lock:
|
|
187
|
-
return list(self._resources)
|
|
188
|
-
|
|
189
|
-
def changed_resources(self) -> Sequence[ResourceIdT]:
|
|
190
|
-
"""
|
|
191
|
-
Get all resource ids from this layer that have changed since the last call to mark_saved.
|
|
192
|
-
:return:
|
|
193
|
-
"""
|
|
194
|
-
with self._h.lock:
|
|
195
|
-
return [
|
|
196
|
-
resource_id
|
|
197
|
-
for resource_id, resource in self._resources.items()
|
|
198
|
-
if resource.saved_index != resource.index
|
|
199
|
-
]
|
|
200
|
-
|
|
201
|
-
def resources_exist_map(self) -> Mapping[ResourceIdT, bool]:
|
|
202
|
-
"""
|
|
203
|
-
Get a mapping from the resource ids to a bool stating if the data exists for that resource.
|
|
204
|
-
If false that resource has been deleted.
|
|
205
|
-
"""
|
|
206
|
-
with self._h.lock:
|
|
207
|
-
return {
|
|
208
|
-
resource_id: resource.exists[resource.index]
|
|
209
|
-
for resource_id, resource in self._resources.items()
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
def has_resource(self, resource_id: ResourceIdT) -> bool:
|
|
213
|
-
"""
|
|
214
|
-
Check if a resource entry exists.
|
|
215
|
-
If the resource has been loaded this will be True regardless if the resource data has been deleted.
|
|
216
|
-
Use :meth:`resource_exists` to check if the resource data has been deleted.
|
|
217
|
-
Calling :meth:`get_resource` or :meth:`set_resource` when this is False will error.
|
|
218
|
-
:param resource_id: The resource identifier
|
|
219
|
-
:return:
|
|
220
|
-
"""
|
|
221
|
-
with self._h.lock:
|
|
222
|
-
return resource_id in self._resources
|
|
223
|
-
|
|
224
|
-
def resource_exists(self, resource_id: ResourceIdT) -> bool:
|
|
225
|
-
"""
|
|
226
|
-
A fast way to check if the resource data exists without loading it.
|
|
227
|
-
:param resource_id: The resource identifier
|
|
228
|
-
:return: True if the data exists.
|
|
229
|
-
"""
|
|
230
|
-
with self._h.lock:
|
|
231
|
-
resource = self._resources[resource_id]
|
|
232
|
-
return resource.exists[resource.index]
|
|
233
|
-
|
|
234
|
-
def get_resource(self, resource_id: ResourceIdT) -> bytes:
|
|
235
|
-
"""
|
|
236
|
-
Get the newest resource data.
|
|
237
|
-
:param resource_id: The resource identifier
|
|
238
|
-
:return: The binary data that was previously set. An empty bytes object for the deleted state.
|
|
239
|
-
"""
|
|
240
|
-
with self._h.lock:
|
|
241
|
-
resource = self._resources[resource_id]
|
|
242
|
-
if resource.exists[resource.index]:
|
|
243
|
-
return self._h.cache[resource.get_resource_key(self._uuid, resource_id)]
|
|
244
|
-
else:
|
|
245
|
-
return b""
|
|
246
|
-
|
|
247
|
-
def set_initial_resource(self, resource_id: ResourceIdT, data: bytes) -> None:
|
|
248
|
-
"""
|
|
249
|
-
Set the data for the resource.
|
|
250
|
-
This can only be used if the resource does not already exist.
|
|
251
|
-
:param resource_id: The resource identifier
|
|
252
|
-
:param data: The binary data to set. An empty bytes object for the deleted state.
|
|
253
|
-
:return:
|
|
254
|
-
"""
|
|
255
|
-
with self._h.lock:
|
|
256
|
-
if resource_id in self._resources:
|
|
257
|
-
raise RuntimeError("Resource already exists")
|
|
258
|
-
resource = self._resources[resource_id] = Resource()
|
|
259
|
-
if data:
|
|
260
|
-
# Save the data to the cache if it exists
|
|
261
|
-
self._h.cache[resource.get_resource_key(self._uuid, resource_id)] = data
|
|
262
|
-
# Store a flag if it exists
|
|
263
|
-
resource.exists[resource.index] = bool(data)
|
|
264
|
-
|
|
265
|
-
def set_resource(self, resource_id: ResourceIdT, data: bytes) -> None:
|
|
266
|
-
"""
|
|
267
|
-
Set the data for the resource.
|
|
268
|
-
:param resource_id: The resource identifier
|
|
269
|
-
:param data: The binary data to set. An empty bytes object for the deleted state.
|
|
270
|
-
:return:
|
|
271
|
-
"""
|
|
272
|
-
with self._h.lock:
|
|
273
|
-
self._h.invalidate_future()
|
|
274
|
-
resource = self._resources[resource_id]
|
|
275
|
-
if resource.global_index != self._h.history_index:
|
|
276
|
-
# The global history index has been increased since the last change to this resource
|
|
277
|
-
# Add a new state
|
|
278
|
-
resource.index += 1
|
|
279
|
-
resource.global_index = self._h.history_index
|
|
280
|
-
resource.exists.append(False)
|
|
281
|
-
if resource.index == resource.saved_index:
|
|
282
|
-
# The saved index has been directly modified
|
|
283
|
-
resource.saved_index = -1
|
|
284
|
-
if data:
|
|
285
|
-
# Save the data to the cache if it exists
|
|
286
|
-
self._h.cache[resource.get_resource_key(self._uuid, resource_id)] = data
|
|
287
|
-
# Store a flag if it exists
|
|
288
|
-
resource.exists[resource.index] = bool(data)
|
|
289
|
-
if self._h.history_index:
|
|
290
|
-
# Add the resource to the history bin if one has been created
|
|
291
|
-
self._h.history[self._h.history_index].add(resource)
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
from abc import ABC, abstractmethod
|
|
3
|
-
from typing import TYPE_CHECKING, Any
|
|
4
|
-
|
|
5
|
-
from amulet.utils.call_spec import method_spec
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from ._level import Level
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class CreatableLevel(ABC):
|
|
13
|
-
"""Level extension class for levels that can be created without data."""
|
|
14
|
-
|
|
15
|
-
__slots__ = ()
|
|
16
|
-
|
|
17
|
-
@classmethod
|
|
18
|
-
@abstractmethod
|
|
19
|
-
@method_spec()
|
|
20
|
-
def create(cls, *args: Any, **kwargs: Any) -> Level:
|
|
21
|
-
"""
|
|
22
|
-
Create a new instance without any existing data.
|
|
23
|
-
You must call :meth:`~amulet.level.abc.Level.open` to open the level for editing.
|
|
24
|
-
:return: A new Level instance
|
|
25
|
-
"""
|
|
26
|
-
# If writing data to disk, it must write a valid level.
|
|
27
|
-
# If only setting attributes, the open method must be aware that it should not load data from disk.
|
|
28
|
-
raise NotImplementedError
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from abc import abstractmethod
|
|
4
|
-
|
|
5
|
-
from ._level import Level, OpenLevelDataT, RawLevelT, DimensionT
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class DiskLevel(Level[OpenLevelDataT, DimensionT, RawLevelT]):
|
|
9
|
-
"""A level base class for all levels with data on disk."""
|
|
10
|
-
|
|
11
|
-
__slots__ = ()
|
|
12
|
-
|
|
13
|
-
@property
|
|
14
|
-
@abstractmethod
|
|
15
|
-
def path(self) -> str:
|
|
16
|
-
"""The path to the level on disk."""
|
|
17
|
-
raise NotImplementedError
|