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.

Files changed (198) hide show
  1. amulet/__init__.py +27 -27
  2. amulet/__pyinstaller/__init__.py +2 -2
  3. amulet/__pyinstaller/hook-amulet.py +4 -4
  4. amulet/_version.py +21 -21
  5. amulet/api/__init__.py +2 -2
  6. amulet/api/abstract_base_entity.py +128 -128
  7. amulet/api/block.py +630 -630
  8. amulet/api/block_entity.py +71 -71
  9. amulet/api/cache.py +107 -107
  10. amulet/api/chunk/__init__.py +6 -6
  11. amulet/api/chunk/biomes.py +207 -207
  12. amulet/api/chunk/block_entity_dict.py +175 -175
  13. amulet/api/chunk/blocks.py +46 -46
  14. amulet/api/chunk/chunk.py +389 -389
  15. amulet/api/chunk/entity_list.py +75 -75
  16. amulet/api/chunk/status.py +167 -167
  17. amulet/api/data_types/__init__.py +4 -4
  18. amulet/api/data_types/generic_types.py +4 -4
  19. amulet/api/data_types/operation_types.py +16 -16
  20. amulet/api/data_types/world_types.py +49 -49
  21. amulet/api/data_types/wrapper_types.py +71 -71
  22. amulet/api/entity.py +74 -74
  23. amulet/api/errors.py +119 -119
  24. amulet/api/history/__init__.py +36 -36
  25. amulet/api/history/base/__init__.py +3 -3
  26. amulet/api/history/base/base_history.py +26 -26
  27. amulet/api/history/base/history_manager.py +63 -63
  28. amulet/api/history/base/revision_manager.py +73 -73
  29. amulet/api/history/changeable.py +15 -15
  30. amulet/api/history/data_types.py +7 -7
  31. amulet/api/history/history_manager/__init__.py +3 -3
  32. amulet/api/history/history_manager/container.py +102 -102
  33. amulet/api/history/history_manager/database.py +279 -279
  34. amulet/api/history/history_manager/meta.py +93 -93
  35. amulet/api/history/history_manager/object.py +116 -116
  36. amulet/api/history/revision_manager/__init__.py +2 -2
  37. amulet/api/history/revision_manager/disk.py +33 -33
  38. amulet/api/history/revision_manager/ram.py +12 -12
  39. amulet/api/item.py +75 -75
  40. amulet/api/level/__init__.py +4 -4
  41. amulet/api/level/base_level/__init__.py +1 -1
  42. amulet/api/level/base_level/base_level.py +1035 -1026
  43. amulet/api/level/base_level/chunk_manager.py +227 -227
  44. amulet/api/level/base_level/clone.py +389 -389
  45. amulet/api/level/base_level/player_manager.py +101 -101
  46. amulet/api/level/immutable_structure/__init__.py +1 -1
  47. amulet/api/level/immutable_structure/immutable_structure.py +94 -94
  48. amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
  49. amulet/api/level/structure.py +22 -22
  50. amulet/api/level/world.py +19 -19
  51. amulet/api/partial_3d_array/__init__.py +2 -2
  52. amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
  53. amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
  54. amulet/api/partial_3d_array/data_types.py +15 -15
  55. amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
  56. amulet/api/partial_3d_array/util.py +152 -152
  57. amulet/api/player.py +65 -65
  58. amulet/api/registry/__init__.py +2 -2
  59. amulet/api/registry/base_registry.py +34 -34
  60. amulet/api/registry/biome_manager.py +153 -153
  61. amulet/api/registry/block_manager.py +156 -156
  62. amulet/api/selection/__init__.py +2 -2
  63. amulet/api/selection/abstract_selection.py +315 -315
  64. amulet/api/selection/box.py +805 -805
  65. amulet/api/selection/group.py +488 -488
  66. amulet/api/structure.py +37 -37
  67. amulet/api/wrapper/__init__.py +8 -8
  68. amulet/api/wrapper/chunk/interface.py +441 -441
  69. amulet/api/wrapper/chunk/translator.py +567 -567
  70. amulet/api/wrapper/format_wrapper.py +772 -772
  71. amulet/api/wrapper/structure_format_wrapper.py +116 -116
  72. amulet/api/wrapper/world_format_wrapper.py +63 -63
  73. amulet/level/__init__.py +1 -1
  74. amulet/level/formats/anvil_forge_world.py +40 -40
  75. amulet/level/formats/anvil_world/__init__.py +3 -3
  76. amulet/level/formats/anvil_world/_sector_manager.py +291 -384
  77. amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
  78. amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
  79. amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
  80. amulet/level/formats/anvil_world/dimension.py +177 -177
  81. amulet/level/formats/anvil_world/format.py +769 -769
  82. amulet/level/formats/anvil_world/region.py +384 -384
  83. amulet/level/formats/construction/__init__.py +3 -3
  84. amulet/level/formats/construction/format_wrapper.py +515 -515
  85. amulet/level/formats/construction/interface.py +134 -134
  86. amulet/level/formats/construction/section.py +60 -60
  87. amulet/level/formats/construction/util.py +165 -165
  88. amulet/level/formats/leveldb_world/__init__.py +3 -3
  89. amulet/level/formats/leveldb_world/chunk.py +33 -33
  90. amulet/level/formats/leveldb_world/dimension.py +385 -419
  91. amulet/level/formats/leveldb_world/format.py +659 -641
  92. amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
  93. amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
  94. amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
  95. amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
  96. amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
  97. amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
  98. amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
  99. amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
  100. amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
  101. amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
  102. amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
  103. amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
  104. amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
  105. amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
  106. amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
  107. amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
  108. amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
  109. amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
  110. amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
  111. amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
  112. amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
  113. amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
  114. amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
  115. amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
  116. amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
  117. amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
  118. amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
  119. amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
  120. amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
  121. amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
  122. amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
  123. amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
  124. amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
  125. amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
  126. amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
  127. amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
  128. amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
  129. amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
  130. amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
  131. amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
  132. amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
  133. amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
  134. amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
  135. amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
  136. amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
  137. amulet/level/formats/mcstructure/__init__.py +3 -3
  138. amulet/level/formats/mcstructure/chunk.py +50 -50
  139. amulet/level/formats/mcstructure/format_wrapper.py +408 -408
  140. amulet/level/formats/mcstructure/interface.py +175 -175
  141. amulet/level/formats/schematic/__init__.py +3 -3
  142. amulet/level/formats/schematic/chunk.py +55 -55
  143. amulet/level/formats/schematic/data_types.py +4 -4
  144. amulet/level/formats/schematic/format_wrapper.py +373 -373
  145. amulet/level/formats/schematic/interface.py +142 -142
  146. amulet/level/formats/sponge_schem/__init__.py +4 -4
  147. amulet/level/formats/sponge_schem/chunk.py +62 -62
  148. amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
  149. amulet/level/formats/sponge_schem/interface.py +118 -118
  150. amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
  151. amulet/level/formats/sponge_schem/varint/varint.py +87 -87
  152. amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
  153. amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
  154. amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
  155. amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
  156. amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
  157. amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
  158. amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
  159. amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
  160. amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
  161. amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
  162. amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
  163. amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
  164. amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
  165. amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
  166. amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
  167. amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
  168. amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
  169. amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
  170. amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
  171. amulet/level/load.py +59 -59
  172. amulet/level/loader.py +95 -95
  173. amulet/level/translators/chunk/bedrock/__init__.py +267 -267
  174. amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
  175. amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
  176. amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
  177. amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
  178. amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
  179. amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
  180. amulet/libs/leveldb/__init__.py +7 -7
  181. amulet/operations/__init__.py +5 -5
  182. amulet/operations/clone.py +18 -18
  183. amulet/operations/delete_chunk.py +32 -32
  184. amulet/operations/fill.py +30 -30
  185. amulet/operations/paste.py +65 -65
  186. amulet/operations/replace.py +58 -58
  187. amulet/utils/__init__.py +14 -14
  188. amulet/utils/format_utils.py +41 -41
  189. amulet/utils/generator.py +15 -15
  190. amulet/utils/matrix.py +243 -243
  191. amulet/utils/numpy_helpers.py +46 -46
  192. amulet/utils/world_utils.py +349 -349
  193. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
  194. amulet_core-1.9.20.dist-info/RECORD +208 -0
  195. amulet_core-1.9.19.dist-info/RECORD +0 -208
  196. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
  197. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
  198. {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
- return sector
174
- elif self._resizable:
175
- sector = Sector(self._stop, self._stop + length)
176
- self._stop = sector.stop
177
- self._reserved.add(sector)
178
- return sector
179
- else:
180
- raise NoValidSector(
181
- "There is not enough contiguous space to allocate the length."
182
- )
183
-
184
- def reserve(self, sector: Sector):
185
- """
186
- Mark a section as reserved.
187
- If you don't know exactly where the sector is use `reserve_space` to find and reserve a new sector
188
-
189
- :param sector: The sector to reserve
190
- """
191
- if not sector.length:
192
- raise ValueError("Cannot reserve a sector with zero length.")
193
- with self._lock:
194
- if sector.start >= self._stop and (
195
- not self._free_start or self._free_start[-1].stop != self._stop
196
- ):
197
- if self._resizable:
198
- # if the last sector has been reserved or did not exist then create a new one
199
- s = Sector(self._stop, sector.stop)
200
- self._free_start.append(s)
201
- self._add_size_sector(s)
202
- self._stop = sector.stop
203
- else:
204
- raise NoValidSector("The sector starts outside of the region.")
205
-
206
- # Get the index of the segment with the latest start point that is
207
- # less than or equal to the start point of the sector being reserved.
208
- index = (
209
- bisect_right(self._free_start, sector.start, key=lambda k: k.start) - 1
210
- )
211
-
212
- if index < 0:
213
- raise NoValidSector(
214
- "No free sectors that start at or before the one being reserved."
215
- )
216
-
217
- free_sector = self._free_start[index]
218
-
219
- if sector.stop <= free_sector.stop:
220
- # The sector fits within the contained sector
221
- # remove the contained sector from the lists
222
- del self._free_start[index]
223
- self._free_size.remove(free_sector)
224
- elif free_sector.stop == self._stop:
225
- if self._resizable:
226
- # The sector is the last one and the memory region is resizable
227
- del self._free_start[index]
228
- self._free_size.remove(free_sector)
229
- free_sector = Sector(free_sector.start, sector.stop)
230
- self._stop = sector.stop
231
- else:
232
- raise NoValidSector(
233
- "The sector is outside the defined region and the region is not resizable."
234
- )
235
- else:
236
- raise NoValidSector("The requested sector is not free to be reserved.")
237
-
238
- # split around the reserved sector
239
- sectors = free_sector.split(sector)
240
- # add the results back
241
- self._free_start[index:index] = sectors
242
- for s in sectors:
243
- self._add_size_sector(s)
244
- self._reserved.add(sector)
245
-
246
- def _add_size_sector(self, sector: Sector):
247
- self._free_size.insert(
248
- bisect_left(
249
- self._free_size,
250
- (sector.length, sector.start),
251
- key=lambda k: (k.length, k.start),
252
- ),
253
- sector,
254
- )
255
-
256
- def free(self, sector: Sector):
257
- """
258
- Free a reserved sector.
259
- The sector must match exactly a sector previously reserved.
260
-
261
- :param sector: The sector to free
262
- """
263
- with self._lock:
264
- # remove the sector from the reserved storage
265
- self._reserved.remove(sector)
266
-
267
- # Add it back to the free storage
268
- # find the position where it would be placed in the list ordered by start location
269
- index = bisect_right(self._free_start, sector.start, key=lambda k: k.start)
270
-
271
- # merge with the right neighbour
272
- if (
273
- index < len(self._free_start)
274
- and self._free_start[index].start == sector.stop
275
- ):
276
- right_sector = self._free_start.pop(index)
277
- self._free_size.remove(right_sector)
278
- sector = Sector(sector.start, right_sector.stop)
279
-
280
- # merge with the left neighbour
281
- if index > 0 and self._free_start[index - 1].stop == sector.start:
282
- left_sector = self._free_start.pop(index - 1)
283
- self._free_size.remove(left_sector)
284
- sector = Sector(left_sector.start, sector.stop)
285
- index -= 1
286
-
287
- self._free_start.insert(index, sector)
288
- self._add_size_sector(sector)
289
-
290
-
291
- def validate(m):
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)