amulet-core 1.9.19__py3-none-any.whl → 1.9.20__py3-none-any.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/__init__.py +27 -27
- amulet/__pyinstaller/__init__.py +2 -2
- amulet/__pyinstaller/hook-amulet.py +4 -4
- amulet/_version.py +21 -21
- amulet/api/__init__.py +2 -2
- amulet/api/abstract_base_entity.py +128 -128
- amulet/api/block.py +630 -630
- amulet/api/block_entity.py +71 -71
- amulet/api/cache.py +107 -107
- amulet/api/chunk/__init__.py +6 -6
- amulet/api/chunk/biomes.py +207 -207
- amulet/api/chunk/block_entity_dict.py +175 -175
- amulet/api/chunk/blocks.py +46 -46
- amulet/api/chunk/chunk.py +389 -389
- amulet/api/chunk/entity_list.py +75 -75
- amulet/api/chunk/status.py +167 -167
- amulet/api/data_types/__init__.py +4 -4
- amulet/api/data_types/generic_types.py +4 -4
- amulet/api/data_types/operation_types.py +16 -16
- amulet/api/data_types/world_types.py +49 -49
- amulet/api/data_types/wrapper_types.py +71 -71
- amulet/api/entity.py +74 -74
- amulet/api/errors.py +119 -119
- amulet/api/history/__init__.py +36 -36
- amulet/api/history/base/__init__.py +3 -3
- amulet/api/history/base/base_history.py +26 -26
- amulet/api/history/base/history_manager.py +63 -63
- amulet/api/history/base/revision_manager.py +73 -73
- amulet/api/history/changeable.py +15 -15
- amulet/api/history/data_types.py +7 -7
- amulet/api/history/history_manager/__init__.py +3 -3
- amulet/api/history/history_manager/container.py +102 -102
- amulet/api/history/history_manager/database.py +279 -279
- amulet/api/history/history_manager/meta.py +93 -93
- amulet/api/history/history_manager/object.py +116 -116
- amulet/api/history/revision_manager/__init__.py +2 -2
- amulet/api/history/revision_manager/disk.py +33 -33
- amulet/api/history/revision_manager/ram.py +12 -12
- amulet/api/item.py +75 -75
- amulet/api/level/__init__.py +4 -4
- amulet/api/level/base_level/__init__.py +1 -1
- amulet/api/level/base_level/base_level.py +1035 -1026
- amulet/api/level/base_level/chunk_manager.py +227 -227
- amulet/api/level/base_level/clone.py +389 -389
- amulet/api/level/base_level/player_manager.py +101 -101
- amulet/api/level/immutable_structure/__init__.py +1 -1
- amulet/api/level/immutable_structure/immutable_structure.py +94 -94
- amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
- amulet/api/level/structure.py +22 -22
- amulet/api/level/world.py +19 -19
- amulet/api/partial_3d_array/__init__.py +2 -2
- amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
- amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
- amulet/api/partial_3d_array/data_types.py +15 -15
- amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
- amulet/api/partial_3d_array/util.py +152 -152
- amulet/api/player.py +65 -65
- amulet/api/registry/__init__.py +2 -2
- amulet/api/registry/base_registry.py +34 -34
- amulet/api/registry/biome_manager.py +153 -153
- amulet/api/registry/block_manager.py +156 -156
- amulet/api/selection/__init__.py +2 -2
- amulet/api/selection/abstract_selection.py +315 -315
- amulet/api/selection/box.py +805 -805
- amulet/api/selection/group.py +488 -488
- amulet/api/structure.py +37 -37
- amulet/api/wrapper/__init__.py +8 -8
- amulet/api/wrapper/chunk/interface.py +441 -441
- amulet/api/wrapper/chunk/translator.py +567 -567
- amulet/api/wrapper/format_wrapper.py +772 -772
- amulet/api/wrapper/structure_format_wrapper.py +116 -116
- amulet/api/wrapper/world_format_wrapper.py +63 -63
- amulet/level/__init__.py +1 -1
- amulet/level/formats/anvil_forge_world.py +40 -40
- amulet/level/formats/anvil_world/__init__.py +3 -3
- amulet/level/formats/anvil_world/_sector_manager.py +291 -384
- amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
- amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
- amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
- amulet/level/formats/anvil_world/dimension.py +177 -177
- amulet/level/formats/anvil_world/format.py +769 -769
- amulet/level/formats/anvil_world/region.py +384 -384
- amulet/level/formats/construction/__init__.py +3 -3
- amulet/level/formats/construction/format_wrapper.py +515 -515
- amulet/level/formats/construction/interface.py +134 -134
- amulet/level/formats/construction/section.py +60 -60
- amulet/level/formats/construction/util.py +165 -165
- amulet/level/formats/leveldb_world/__init__.py +3 -3
- amulet/level/formats/leveldb_world/chunk.py +33 -33
- amulet/level/formats/leveldb_world/dimension.py +385 -419
- amulet/level/formats/leveldb_world/format.py +659 -641
- amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
- amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
- amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
- amulet/level/formats/mcstructure/__init__.py +3 -3
- amulet/level/formats/mcstructure/chunk.py +50 -50
- amulet/level/formats/mcstructure/format_wrapper.py +408 -408
- amulet/level/formats/mcstructure/interface.py +175 -175
- amulet/level/formats/schematic/__init__.py +3 -3
- amulet/level/formats/schematic/chunk.py +55 -55
- amulet/level/formats/schematic/data_types.py +4 -4
- amulet/level/formats/schematic/format_wrapper.py +373 -373
- amulet/level/formats/schematic/interface.py +142 -142
- amulet/level/formats/sponge_schem/__init__.py +4 -4
- amulet/level/formats/sponge_schem/chunk.py +62 -62
- amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
- amulet/level/formats/sponge_schem/interface.py +118 -118
- amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
- amulet/level/formats/sponge_schem/varint/varint.py +87 -87
- amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
- amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
- amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
- amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
- amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
- amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
- amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
- amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
- amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
- amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
- amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
- amulet/level/load.py +59 -59
- amulet/level/loader.py +95 -95
- amulet/level/translators/chunk/bedrock/__init__.py +267 -267
- amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
- amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
- amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
- amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
- amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
- amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
- amulet/libs/leveldb/__init__.py +7 -7
- amulet/operations/__init__.py +5 -5
- amulet/operations/clone.py +18 -18
- amulet/operations/delete_chunk.py +32 -32
- amulet/operations/fill.py +30 -30
- amulet/operations/paste.py +65 -65
- amulet/operations/replace.py +58 -58
- amulet/utils/__init__.py +14 -14
- amulet/utils/format_utils.py +41 -41
- amulet/utils/generator.py +15 -15
- amulet/utils/matrix.py +243 -243
- amulet/utils/numpy_helpers.py +46 -46
- amulet/utils/world_utils.py +349 -349
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
- amulet_core-1.9.20.dist-info/RECORD +208 -0
- amulet_core-1.9.19.dist-info/RECORD +0 -208
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
|
@@ -1,384 +1,291 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import NamedTuple, List
|
|
4
|
-
import threading
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
|
-
if sys.version_info >= (3, 10):
|
|
8
|
-
# bisect only supports key as of 3.10
|
|
9
|
-
from bisect import bisect_left, bisect_right
|
|
10
|
-
|
|
11
|
-
else:
|
|
12
|
-
|
|
13
|
-
def bisect_left(a, x, lo=0, hi=None, *, key=None):
|
|
14
|
-
"""Return the index where to insert item x in list a, assuming a is sorted.
|
|
15
|
-
The return value i is such that all e in a[:i] have e < x, and all e in
|
|
16
|
-
a[i:] have e >= x. So if x already appears in the list, a.insert(i, x) will
|
|
17
|
-
insert just before the leftmost x already there.
|
|
18
|
-
Optional args lo (default 0) and hi (default len(a)) bound the
|
|
19
|
-
slice of a to be searched.
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
if lo < 0:
|
|
23
|
-
raise ValueError("lo must be non-negative")
|
|
24
|
-
if hi is None:
|
|
25
|
-
hi = len(a)
|
|
26
|
-
# Note, the comparison uses "<" to match the
|
|
27
|
-
# __lt__() logic in list.sort() and in heapq.
|
|
28
|
-
if key is None:
|
|
29
|
-
while lo < hi:
|
|
30
|
-
mid = (lo + hi) // 2
|
|
31
|
-
if a[mid] < x:
|
|
32
|
-
lo = mid + 1
|
|
33
|
-
else:
|
|
34
|
-
hi = mid
|
|
35
|
-
else:
|
|
36
|
-
while lo < hi:
|
|
37
|
-
mid = (lo + hi) // 2
|
|
38
|
-
if key(a[mid]) < x:
|
|
39
|
-
lo = mid + 1
|
|
40
|
-
else:
|
|
41
|
-
hi = mid
|
|
42
|
-
return lo
|
|
43
|
-
|
|
44
|
-
def bisect_right(a, x, lo=0, hi=None, *, key=None):
|
|
45
|
-
"""Return the index where to insert item x in list a, assuming a is sorted.
|
|
46
|
-
The return value i is such that all e in a[:i] have e <= x, and all e in
|
|
47
|
-
a[i:] have e > x. So if x already appears in the list, a.insert(i, x) will
|
|
48
|
-
insert just after the rightmost x already there.
|
|
49
|
-
Optional args lo (default 0) and hi (default len(a)) bound the
|
|
50
|
-
slice of a to be searched.
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
if lo < 0:
|
|
54
|
-
raise ValueError("lo must be non-negative")
|
|
55
|
-
if hi is None:
|
|
56
|
-
hi = len(a)
|
|
57
|
-
# Note, the comparison uses "<" to match the
|
|
58
|
-
# __lt__() logic in list.sort() and in heapq.
|
|
59
|
-
if key is None:
|
|
60
|
-
while lo < hi:
|
|
61
|
-
mid = (lo + hi) // 2
|
|
62
|
-
if x < a[mid]:
|
|
63
|
-
hi = mid
|
|
64
|
-
else:
|
|
65
|
-
lo = mid + 1
|
|
66
|
-
else:
|
|
67
|
-
while lo < hi:
|
|
68
|
-
mid = (lo + hi) // 2
|
|
69
|
-
if x < key(a[mid]):
|
|
70
|
-
hi = mid
|
|
71
|
-
else:
|
|
72
|
-
lo = mid + 1
|
|
73
|
-
return lo
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class NoValidSector(Exception):
|
|
77
|
-
"""An error for when there is no sector large enough and the region cannot be resized."""
|
|
78
|
-
|
|
79
|
-
pass
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class Sector(NamedTuple):
|
|
83
|
-
start: int
|
|
84
|
-
stop: int
|
|
85
|
-
|
|
86
|
-
@property
|
|
87
|
-
def length(self) -> int:
|
|
88
|
-
return self.stop - self.start
|
|
89
|
-
|
|
90
|
-
def intersects(self, other: Sector):
|
|
91
|
-
"""Do the two sectors intersect each other."""
|
|
92
|
-
return not (other.stop <= self.start or self.stop <= other.start)
|
|
93
|
-
|
|
94
|
-
def contains(self, other: Sector):
|
|
95
|
-
"""Is the other sector entirely within this sector."""
|
|
96
|
-
return self.start <= other.start and other.stop <= self.stop
|
|
97
|
-
|
|
98
|
-
def neighbours(self, other: Sector):
|
|
99
|
-
"""Do the two sectors neighbour but not intersect."""
|
|
100
|
-
return other.stop == self.start or self.stop == other.start
|
|
101
|
-
|
|
102
|
-
def split(self, other: Sector) -> List[Sector]:
|
|
103
|
-
"""
|
|
104
|
-
Split this sector around another sector.
|
|
105
|
-
The other sector must be contained within this sector
|
|
106
|
-
|
|
107
|
-
:param other: The other sector to split around.
|
|
108
|
-
:return: A list of 0-2 sectors
|
|
109
|
-
"""
|
|
110
|
-
sectors = []
|
|
111
|
-
if self.start < other.start:
|
|
112
|
-
sectors.append(Sector(self.start, other.start))
|
|
113
|
-
if other.stop < self.stop:
|
|
114
|
-
sectors.append(Sector(other.stop, self.stop))
|
|
115
|
-
return sectors
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
class SectorManager:
|
|
119
|
-
"""A class to manage a sequence of memory."""
|
|
120
|
-
|
|
121
|
-
def __init__(self, start: int, stop: int, resizable: bool = True):
|
|
122
|
-
"""
|
|
123
|
-
:param start: The start of the memory region
|
|
124
|
-
:param stop: The end of the memory region
|
|
125
|
-
:param resizable: Can the region be resized
|
|
126
|
-
"""
|
|
127
|
-
if stop < start:
|
|
128
|
-
raise ValueError("stop must be at least start")
|
|
129
|
-
self._lock = threading.RLock()
|
|
130
|
-
self._stop = stop
|
|
131
|
-
self._resizable = resizable
|
|
132
|
-
|
|
133
|
-
# A list of free sectors ordered by start location
|
|
134
|
-
self._free_start = [Sector(start, stop)]
|
|
135
|
-
# A list of free sectors ordered by (length, start).
|
|
136
|
-
# This makes it easier to find the first sector large enough
|
|
137
|
-
self._free_size = [Sector(start, stop)]
|
|
138
|
-
|
|
139
|
-
# A set of reserved sectors
|
|
140
|
-
self._reserved = set()
|
|
141
|
-
|
|
142
|
-
@property
|
|
143
|
-
def sectors(self) -> List[Sector]:
|
|
144
|
-
"""A list of reserved sectors"""
|
|
145
|
-
with self._lock:
|
|
146
|
-
return list(self._reserved)
|
|
147
|
-
|
|
148
|
-
def reserve_space(self, length: int) -> Sector:
|
|
149
|
-
"""
|
|
150
|
-
Find and reserve a memory location large enough to fit the requested memory.
|
|
151
|
-
|
|
152
|
-
:param length: The length of the memory region to reserve
|
|
153
|
-
:return: The index of the start of the reserved memory region
|
|
154
|
-
"""
|
|
155
|
-
if not length:
|
|
156
|
-
raise ValueError("Cannot reserve a sector with zero length.")
|
|
157
|
-
with self._lock:
|
|
158
|
-
# find the index of the first element with a larger or equal length prioritising the ones closer to the start
|
|
159
|
-
index = bisect_left(
|
|
160
|
-
self._free_size, (length, 0), key=lambda k: (k.length, k.start)
|
|
161
|
-
)
|
|
162
|
-
if index < len(self._free_size):
|
|
163
|
-
# if there exists a section large enough to fit the length
|
|
164
|
-
free_sector = self._free_size.pop(index)
|
|
165
|
-
start_index = self._free_start.index(free_sector)
|
|
166
|
-
sector = Sector(free_sector.start, free_sector.start + length)
|
|
167
|
-
if free_sector.length > length:
|
|
168
|
-
free_sector = Sector(sector.stop, free_sector.stop)
|
|
169
|
-
self._add_size_sector(free_sector)
|
|
170
|
-
self._free_start[start_index] = free_sector
|
|
171
|
-
else:
|
|
172
|
-
del self._free_start[start_index]
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
self._stop
|
|
177
|
-
self.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
self.
|
|
201
|
-
self.
|
|
202
|
-
self.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
#
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
#
|
|
222
|
-
|
|
223
|
-
self.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
self.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
self._reserved
|
|
266
|
-
|
|
267
|
-
#
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
#
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
self.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
index
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
assert set(m._free_start) == set(m._free_size)
|
|
293
|
-
free = sorted(list(m._free_start) + list(m._reserved), key=lambda k: k.start)
|
|
294
|
-
if free:
|
|
295
|
-
for i in range(len(free) - 1):
|
|
296
|
-
assert free[i].stop == free[i + 1].start
|
|
297
|
-
assert free[-1].stop == m._stop
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
def test1():
|
|
301
|
-
m = SectorManager(2, 3)
|
|
302
|
-
validate(m)
|
|
303
|
-
m.reserve(Sector(2, 3))
|
|
304
|
-
validate(m)
|
|
305
|
-
m.reserve(Sector(3, 4))
|
|
306
|
-
validate(m)
|
|
307
|
-
print(m.sectors)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def test2():
|
|
311
|
-
m = SectorManager(2, 102)
|
|
312
|
-
|
|
313
|
-
m.reserve(Sector(5, 6))
|
|
314
|
-
validate(m)
|
|
315
|
-
m.reserve(Sector(6, 7))
|
|
316
|
-
validate(m)
|
|
317
|
-
# m.reserve(Sector(7, 8))
|
|
318
|
-
m.reserve(Sector(8, 9))
|
|
319
|
-
validate(m)
|
|
320
|
-
|
|
321
|
-
try:
|
|
322
|
-
m.reserve(Sector(6, 8))
|
|
323
|
-
except NoValidSector:
|
|
324
|
-
pass
|
|
325
|
-
else:
|
|
326
|
-
raise Exception
|
|
327
|
-
|
|
328
|
-
validate(m)
|
|
329
|
-
|
|
330
|
-
try:
|
|
331
|
-
m.reserve(Sector(7, 9))
|
|
332
|
-
except NoValidSector:
|
|
333
|
-
pass
|
|
334
|
-
else:
|
|
335
|
-
raise Exception
|
|
336
|
-
|
|
337
|
-
validate(m)
|
|
338
|
-
m.free(Sector(5, 6))
|
|
339
|
-
validate(m)
|
|
340
|
-
m.free(Sector(6, 7))
|
|
341
|
-
validate(m)
|
|
342
|
-
m.free(Sector(8, 9))
|
|
343
|
-
validate(m)
|
|
344
|
-
|
|
345
|
-
m.reserve(Sector(6, 8))
|
|
346
|
-
validate(m)
|
|
347
|
-
m.free(Sector(6, 8))
|
|
348
|
-
validate(m)
|
|
349
|
-
|
|
350
|
-
m.reserve(Sector(7, 9))
|
|
351
|
-
validate(m)
|
|
352
|
-
m.free(Sector(7, 9))
|
|
353
|
-
|
|
354
|
-
validate(m)
|
|
355
|
-
|
|
356
|
-
assert len(m._free_start) == 1
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
# def test3():
|
|
360
|
-
# reserve a number of single length units
|
|
361
|
-
# for _ in range(20):
|
|
362
|
-
# i = free_indexes.pop(random.randrange(len(free_indexes)))
|
|
363
|
-
# m.reserve(Sector(i, i+1))
|
|
364
|
-
# validate(m)
|
|
365
|
-
# reserved_indexes.append(i)
|
|
366
|
-
#
|
|
367
|
-
# for _ in range(20):
|
|
368
|
-
# index = random.randrange(len(free_indexes))
|
|
369
|
-
# di = 1
|
|
370
|
-
# i = free_indexes.pop(index)
|
|
371
|
-
#
|
|
372
|
-
# for _ in range(10_000):
|
|
373
|
-
# m.reserve(Sector(i, i+1))
|
|
374
|
-
# validate(m)
|
|
375
|
-
# print(m.sectors)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
def test():
|
|
379
|
-
test1()
|
|
380
|
-
test2()
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if __name__ == "__main__":
|
|
384
|
-
test()
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import NamedTuple, List
|
|
4
|
+
import threading
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
if sys.version_info >= (3, 10):
|
|
8
|
+
# bisect only supports key as of 3.10
|
|
9
|
+
from bisect import bisect_left, bisect_right
|
|
10
|
+
|
|
11
|
+
else:
|
|
12
|
+
|
|
13
|
+
def bisect_left(a, x, lo=0, hi=None, *, key=None):
|
|
14
|
+
"""Return the index where to insert item x in list a, assuming a is sorted.
|
|
15
|
+
The return value i is such that all e in a[:i] have e < x, and all e in
|
|
16
|
+
a[i:] have e >= x. So if x already appears in the list, a.insert(i, x) will
|
|
17
|
+
insert just before the leftmost x already there.
|
|
18
|
+
Optional args lo (default 0) and hi (default len(a)) bound the
|
|
19
|
+
slice of a to be searched.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
if lo < 0:
|
|
23
|
+
raise ValueError("lo must be non-negative")
|
|
24
|
+
if hi is None:
|
|
25
|
+
hi = len(a)
|
|
26
|
+
# Note, the comparison uses "<" to match the
|
|
27
|
+
# __lt__() logic in list.sort() and in heapq.
|
|
28
|
+
if key is None:
|
|
29
|
+
while lo < hi:
|
|
30
|
+
mid = (lo + hi) // 2
|
|
31
|
+
if a[mid] < x:
|
|
32
|
+
lo = mid + 1
|
|
33
|
+
else:
|
|
34
|
+
hi = mid
|
|
35
|
+
else:
|
|
36
|
+
while lo < hi:
|
|
37
|
+
mid = (lo + hi) // 2
|
|
38
|
+
if key(a[mid]) < x:
|
|
39
|
+
lo = mid + 1
|
|
40
|
+
else:
|
|
41
|
+
hi = mid
|
|
42
|
+
return lo
|
|
43
|
+
|
|
44
|
+
def bisect_right(a, x, lo=0, hi=None, *, key=None):
|
|
45
|
+
"""Return the index where to insert item x in list a, assuming a is sorted.
|
|
46
|
+
The return value i is such that all e in a[:i] have e <= x, and all e in
|
|
47
|
+
a[i:] have e > x. So if x already appears in the list, a.insert(i, x) will
|
|
48
|
+
insert just after the rightmost x already there.
|
|
49
|
+
Optional args lo (default 0) and hi (default len(a)) bound the
|
|
50
|
+
slice of a to be searched.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
if lo < 0:
|
|
54
|
+
raise ValueError("lo must be non-negative")
|
|
55
|
+
if hi is None:
|
|
56
|
+
hi = len(a)
|
|
57
|
+
# Note, the comparison uses "<" to match the
|
|
58
|
+
# __lt__() logic in list.sort() and in heapq.
|
|
59
|
+
if key is None:
|
|
60
|
+
while lo < hi:
|
|
61
|
+
mid = (lo + hi) // 2
|
|
62
|
+
if x < a[mid]:
|
|
63
|
+
hi = mid
|
|
64
|
+
else:
|
|
65
|
+
lo = mid + 1
|
|
66
|
+
else:
|
|
67
|
+
while lo < hi:
|
|
68
|
+
mid = (lo + hi) // 2
|
|
69
|
+
if x < key(a[mid]):
|
|
70
|
+
hi = mid
|
|
71
|
+
else:
|
|
72
|
+
lo = mid + 1
|
|
73
|
+
return lo
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class NoValidSector(Exception):
|
|
77
|
+
"""An error for when there is no sector large enough and the region cannot be resized."""
|
|
78
|
+
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Sector(NamedTuple):
|
|
83
|
+
start: int
|
|
84
|
+
stop: int
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def length(self) -> int:
|
|
88
|
+
return self.stop - self.start
|
|
89
|
+
|
|
90
|
+
def intersects(self, other: Sector):
|
|
91
|
+
"""Do the two sectors intersect each other."""
|
|
92
|
+
return not (other.stop <= self.start or self.stop <= other.start)
|
|
93
|
+
|
|
94
|
+
def contains(self, other: Sector):
|
|
95
|
+
"""Is the other sector entirely within this sector."""
|
|
96
|
+
return self.start <= other.start and other.stop <= self.stop
|
|
97
|
+
|
|
98
|
+
def neighbours(self, other: Sector):
|
|
99
|
+
"""Do the two sectors neighbour but not intersect."""
|
|
100
|
+
return other.stop == self.start or self.stop == other.start
|
|
101
|
+
|
|
102
|
+
def split(self, other: Sector) -> List[Sector]:
|
|
103
|
+
"""
|
|
104
|
+
Split this sector around another sector.
|
|
105
|
+
The other sector must be contained within this sector
|
|
106
|
+
|
|
107
|
+
:param other: The other sector to split around.
|
|
108
|
+
:return: A list of 0-2 sectors
|
|
109
|
+
"""
|
|
110
|
+
sectors = []
|
|
111
|
+
if self.start < other.start:
|
|
112
|
+
sectors.append(Sector(self.start, other.start))
|
|
113
|
+
if other.stop < self.stop:
|
|
114
|
+
sectors.append(Sector(other.stop, self.stop))
|
|
115
|
+
return sectors
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class SectorManager:
|
|
119
|
+
"""A class to manage a sequence of memory."""
|
|
120
|
+
|
|
121
|
+
def __init__(self, start: int, stop: int, resizable: bool = True):
|
|
122
|
+
"""
|
|
123
|
+
:param start: The start of the memory region
|
|
124
|
+
:param stop: The end of the memory region
|
|
125
|
+
:param resizable: Can the region be resized
|
|
126
|
+
"""
|
|
127
|
+
if stop < start:
|
|
128
|
+
raise ValueError("stop must be at least start")
|
|
129
|
+
self._lock = threading.RLock()
|
|
130
|
+
self._stop = stop
|
|
131
|
+
self._resizable = resizable
|
|
132
|
+
|
|
133
|
+
# A list of free sectors ordered by start location
|
|
134
|
+
self._free_start = [Sector(start, stop)]
|
|
135
|
+
# A list of free sectors ordered by (length, start).
|
|
136
|
+
# This makes it easier to find the first sector large enough
|
|
137
|
+
self._free_size = [Sector(start, stop)]
|
|
138
|
+
|
|
139
|
+
# A set of reserved sectors
|
|
140
|
+
self._reserved = set()
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def sectors(self) -> List[Sector]:
|
|
144
|
+
"""A list of reserved sectors. Ordered by their start location."""
|
|
145
|
+
with self._lock:
|
|
146
|
+
return list(sorted(self._reserved, key=lambda i: i.start))
|
|
147
|
+
|
|
148
|
+
def reserve_space(self, length: int) -> Sector:
|
|
149
|
+
"""
|
|
150
|
+
Find and reserve a memory location large enough to fit the requested memory.
|
|
151
|
+
|
|
152
|
+
:param length: The length of the memory region to reserve
|
|
153
|
+
:return: The index of the start of the reserved memory region
|
|
154
|
+
"""
|
|
155
|
+
if not length:
|
|
156
|
+
raise ValueError("Cannot reserve a sector with zero length.")
|
|
157
|
+
with self._lock:
|
|
158
|
+
# find the index of the first element with a larger or equal length prioritising the ones closer to the start
|
|
159
|
+
index = bisect_left(
|
|
160
|
+
self._free_size, (length, 0), key=lambda k: (k.length, k.start)
|
|
161
|
+
)
|
|
162
|
+
if index < len(self._free_size):
|
|
163
|
+
# if there exists a section large enough to fit the length
|
|
164
|
+
free_sector = self._free_size.pop(index)
|
|
165
|
+
start_index = self._free_start.index(free_sector)
|
|
166
|
+
sector = Sector(free_sector.start, free_sector.start + length)
|
|
167
|
+
if free_sector.length > length:
|
|
168
|
+
free_sector = Sector(sector.stop, free_sector.stop)
|
|
169
|
+
self._add_size_sector(free_sector)
|
|
170
|
+
self._free_start[start_index] = free_sector
|
|
171
|
+
else:
|
|
172
|
+
del self._free_start[start_index]
|
|
173
|
+
self._reserved.add(sector)
|
|
174
|
+
return sector
|
|
175
|
+
elif self._resizable:
|
|
176
|
+
sector = Sector(self._stop, self._stop + length)
|
|
177
|
+
self._stop = sector.stop
|
|
178
|
+
self._reserved.add(sector)
|
|
179
|
+
return sector
|
|
180
|
+
else:
|
|
181
|
+
raise NoValidSector(
|
|
182
|
+
"There is not enough contiguous space to allocate the length."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def reserve(self, sector: Sector):
|
|
186
|
+
"""
|
|
187
|
+
Mark a section as reserved.
|
|
188
|
+
If you don't know exactly where the sector is use `reserve_space` to find and reserve a new sector
|
|
189
|
+
|
|
190
|
+
:param sector: The sector to reserve
|
|
191
|
+
"""
|
|
192
|
+
if not sector.length:
|
|
193
|
+
raise ValueError("Cannot reserve a sector with zero length.")
|
|
194
|
+
with self._lock:
|
|
195
|
+
if sector.start >= self._stop and (
|
|
196
|
+
not self._free_start or self._free_start[-1].stop != self._stop
|
|
197
|
+
):
|
|
198
|
+
if self._resizable:
|
|
199
|
+
# if the last sector has been reserved or did not exist then create a new one
|
|
200
|
+
s = Sector(self._stop, sector.stop)
|
|
201
|
+
self._free_start.append(s)
|
|
202
|
+
self._add_size_sector(s)
|
|
203
|
+
self._stop = sector.stop
|
|
204
|
+
else:
|
|
205
|
+
raise NoValidSector("The sector starts outside of the region.")
|
|
206
|
+
|
|
207
|
+
# Get the index of the segment with the latest start point that is
|
|
208
|
+
# less than or equal to the start point of the sector being reserved.
|
|
209
|
+
index = (
|
|
210
|
+
bisect_right(self._free_start, sector.start, key=lambda k: k.start) - 1
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if index < 0:
|
|
214
|
+
raise NoValidSector(
|
|
215
|
+
"No free sectors that start at or before the one being reserved."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
free_sector = self._free_start[index]
|
|
219
|
+
|
|
220
|
+
if sector.stop <= free_sector.stop:
|
|
221
|
+
# The sector fits within the contained sector
|
|
222
|
+
# remove the contained sector from the lists
|
|
223
|
+
del self._free_start[index]
|
|
224
|
+
self._free_size.remove(free_sector)
|
|
225
|
+
elif free_sector.stop == self._stop:
|
|
226
|
+
if self._resizable:
|
|
227
|
+
# The sector is the last one and the memory region is resizable
|
|
228
|
+
del self._free_start[index]
|
|
229
|
+
self._free_size.remove(free_sector)
|
|
230
|
+
free_sector = Sector(free_sector.start, sector.stop)
|
|
231
|
+
self._stop = sector.stop
|
|
232
|
+
else:
|
|
233
|
+
raise NoValidSector(
|
|
234
|
+
"The sector is outside the defined region and the region is not resizable."
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
raise NoValidSector("The requested sector is not free to be reserved.")
|
|
238
|
+
|
|
239
|
+
# split around the reserved sector
|
|
240
|
+
sectors = free_sector.split(sector)
|
|
241
|
+
# add the results back
|
|
242
|
+
self._free_start[index:index] = sectors
|
|
243
|
+
for s in sectors:
|
|
244
|
+
self._add_size_sector(s)
|
|
245
|
+
self._reserved.add(sector)
|
|
246
|
+
|
|
247
|
+
def _add_size_sector(self, sector: Sector):
|
|
248
|
+
self._free_size.insert(
|
|
249
|
+
bisect_left(
|
|
250
|
+
self._free_size,
|
|
251
|
+
(sector.length, sector.start),
|
|
252
|
+
key=lambda k: (k.length, k.start),
|
|
253
|
+
),
|
|
254
|
+
sector,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def free(self, sector: Sector):
|
|
258
|
+
"""
|
|
259
|
+
Free a reserved sector.
|
|
260
|
+
The sector must match exactly a sector previously reserved.
|
|
261
|
+
|
|
262
|
+
:param sector: The sector to free
|
|
263
|
+
"""
|
|
264
|
+
with self._lock:
|
|
265
|
+
if sector not in self._reserved:
|
|
266
|
+
raise ValueError("Sector was not reserved")
|
|
267
|
+
# remove the sector from the reserved storage
|
|
268
|
+
self._reserved.remove(sector)
|
|
269
|
+
|
|
270
|
+
# Add it back to the free storage
|
|
271
|
+
# find the position where it would be placed in the list ordered by start location
|
|
272
|
+
index = bisect_right(self._free_start, sector.start, key=lambda k: k.start)
|
|
273
|
+
|
|
274
|
+
# merge with the right neighbour
|
|
275
|
+
if (
|
|
276
|
+
index < len(self._free_start)
|
|
277
|
+
and self._free_start[index].start == sector.stop
|
|
278
|
+
):
|
|
279
|
+
right_sector = self._free_start.pop(index)
|
|
280
|
+
self._free_size.remove(right_sector)
|
|
281
|
+
sector = Sector(sector.start, right_sector.stop)
|
|
282
|
+
|
|
283
|
+
# merge with the left neighbour
|
|
284
|
+
if index > 0 and self._free_start[index - 1].stop == sector.start:
|
|
285
|
+
left_sector = self._free_start.pop(index - 1)
|
|
286
|
+
self._free_size.remove(left_sector)
|
|
287
|
+
sector = Sector(left_sector.start, sector.stop)
|
|
288
|
+
index -= 1
|
|
289
|
+
|
|
290
|
+
self._free_start.insert(index, sector)
|
|
291
|
+
self._add_size_sector(sector)
|