Synchronize world chunk index updates

This commit is contained in:
Tanner
2026-11-08 17:14:22 +00:00
parent c79ee932a3
commit ea887c7019
3 changed files with 154 additions and 100 deletions
+123 -96
View File
@@ -1,4 +1,5 @@
import json
import threading
import time
from math import floor
from munch import Munch
@@ -218,6 +219,7 @@ class WorldState:
def __init__(self, min_y=-64):
self.min_y = min_y
self.min_section = min_y // 16
self.lock = threading.RLock()
self.chunks = {}
self.index = {}
self.block_entities = {}
@@ -229,68 +231,84 @@ class WorldState:
self.player_id = None
def set_dimension(self, dimension, dimension_type=None):
if dimension:
self.dimension = dimension
if dimension_type:
min_y = dimension_type.get('min_y', self.min_y)
if isinstance(min_y, int):
self.min_y = min_y
self.min_section = min_y // 16
with self.lock:
if dimension:
self.dimension = dimension
if dimension_type:
min_y = dimension_type.get('min_y', self.min_y)
if isinstance(min_y, int):
self.min_y = min_y
self.min_section = min_y // 16
def load_chunk(self, packet):
# Refresh selectors in case block metadata was reloaded before login.
from mosfet.info import blocks
self.indexed_ids = blocks.INDEXED_IDS
self.unload_chunk(packet.x, packet.z)
chunk = ChunkColumn(packet.x, packet.z, packet.sections,
self.min_section)
self.chunks[(packet.x, packet.z)] = chunk
for entity in getattr(packet, 'block_entities', []):
pos = (entity['x'], entity['y'], entity['z'])
self.set_block_entity(
pos, entity['type'], entity.get('data'))
for section_y, section in chunk.sections.items():
palette = section['blocks']
if not self.indexed_ids.intersection(
palette.palette or [palette.get(i) for i in range(palette.size)]):
continue
for index in range(palette.size):
state_id = palette.get(index)
if state_id not in self.indexed_ids:
with self.lock:
# Refresh selectors in case block metadata was reloaded before login.
from mosfet.info import blocks
self.indexed_ids = blocks.INDEXED_IDS
self.unload_chunk(packet.x, packet.z)
chunk = ChunkColumn(packet.x, packet.z, packet.sections,
self.min_section)
self.chunks[(packet.x, packet.z)] = chunk
for entity in getattr(packet, 'block_entities', []):
pos = (entity['x'], entity['y'], entity['z'])
self.set_block_entity(
pos, entity['type'], entity.get('data'))
for section_y, section in chunk.sections.items():
palette = section['blocks']
if not self.indexed_ids.intersection(
palette.palette or [palette.get(i)
for i in range(palette.size)]):
continue
local_x = index & 0xF
local_z = (index >> 4) & 0xF
local_y = (index >> 8) & 0xF
pos = (packet.x * 16 + local_x,
section_y * 16 + local_y,
packet.z * 16 + local_z)
self.index.setdefault(state_id, set()).add(pos)
for index in range(palette.size):
state_id = palette.get(index)
if state_id not in self.indexed_ids:
continue
local_x = index & 0xF
local_z = (index >> 4) & 0xF
local_y = (index >> 8) & 0xF
pos = (packet.x * 16 + local_x,
section_y * 16 + local_y,
packet.z * 16 + local_z)
self.index.setdefault(state_id, set()).add(pos)
def handle_block_update(self, packet):
self.set_block_at(packet.x, packet.y, packet.z, packet.block_state_id)
def handle_section_update(self, packet):
for x, y, z, state_id in packet.changes:
self.set_block_at(x, y, z, state_id)
with self.lock:
for x, y, z, state_id in packet.changes:
self.set_block_at(x, y, z, state_id)
def get_chunk(self, x, z):
return self.chunks.get((x, z))
with self.lock:
return self.chunks.get((x, z))
def get_block_at(self, x, y, z):
chunk = self.get_chunk(floor(x / 16), floor(z / 16))
if chunk is None:
return None
return chunk.get_block_at(floor(x), floor(y), floor(z))
with self.lock:
chunk = self.chunks.get((floor(x / 16), floor(z / 16)))
if chunk is None:
return None
return chunk.get_block_at(floor(x), floor(y), floor(z))
def indexed_positions(self, block_ids):
with self.lock:
return [position for block_id in block_ids
for position in tuple(self.index.get(block_id, ()))]
def block_entities_snapshot(self):
with self.lock:
return dict(self.block_entities)
def set_block_entity(self, position, entity_type, data):
position = tuple(position)
if data is None:
self.block_entities.pop(position, None)
return
self.block_entities[position] = {
'type': entity_type,
'data': nbt_plain(data),
}
with self.lock:
if data is None:
self.block_entities.pop(position, None)
return
self.block_entities[position] = {
'type': entity_type,
'data': nbt_plain(data),
}
@staticmethod
def _is_sign_state(block_state_id):
@@ -300,67 +318,76 @@ class WorldState:
def set_block_at(self, x, y, z, block_state_id):
x, y, z = floor(x), floor(y), floor(z)
chunk = self.get_chunk(floor(x / 16), floor(z / 16))
if chunk is None:
return False
previous = chunk.set_block_at(x, y, z, block_state_id)
pos = (x, y, z)
if previous in self.indexed_ids:
self.index.get(previous, set()).discard(pos)
if (self._is_sign_state(previous) and
not self._is_sign_state(block_state_id)):
self.block_entities.pop(pos, None)
if block_state_id in self.indexed_ids:
self.index.setdefault(block_state_id, set()).add(pos)
return True
with self.lock:
chunk = self.chunks.get((floor(x / 16), floor(z / 16)))
if chunk is None:
return False
previous = chunk.set_block_at(x, y, z, block_state_id)
pos = (x, y, z)
if previous in self.indexed_ids:
self.index.get(previous, set()).discard(pos)
if (self._is_sign_state(previous) and
not self._is_sign_state(block_state_id)):
self.block_entities.pop(pos, None)
if block_state_id in self.indexed_ids:
self.index.setdefault(block_state_id, set()).add(pos)
return True
def unload_chunk(self, x, z):
chunk = self.chunks.pop((x, z), None)
if chunk is None:
return
x0, z0 = x * 16, z * 16
for state_id, locations in list(self.index.items()):
self.index[state_id] = {
pos for pos in locations
if not (x0 <= pos[0] < x0 + 16 and z0 <= pos[2] < z0 + 16)
with self.lock:
chunk = self.chunks.pop((x, z), None)
if chunk is None:
return
x0, z0 = x * 16, z * 16
for state_id, locations in list(self.index.items()):
remaining = {
pos for pos in locations.copy()
if not (x0 <= pos[0] < x0 + 16 and
z0 <= pos[2] < z0 + 16)
}
if remaining:
self.index[state_id] = remaining
else:
self.index.pop(state_id, None)
self.block_entities = {
pos: entity for pos, entity in self.block_entities.items()
if not (x0 <= pos[0] < x0 + 16 and
z0 <= pos[2] < z0 + 16)
}
if not self.index[state_id]:
del self.index[state_id]
self.block_entities = {
pos: entity for pos, entity in self.block_entities.items()
if not (x0 <= pos[0] < x0 + 16 and
z0 <= pos[2] < z0 + 16)
}
def unload_all_chunks(self):
self.chunks.clear()
self.index.clear()
self.block_entities.clear()
with self.lock:
self.chunks.clear()
self.index.clear()
self.block_entities.clear()
def get_loaded_area(self, ignore_empty=False):
if not self.chunks:
return None
xs = [pos[0] for pos in self.chunks]
zs = [pos[1] for pos in self.chunks]
ys = [section_y for chunk in self.chunks.values()
for section_y in chunk.sections]
return ((min(xs), min(ys), min(zs)),
(max(xs), max(ys), max(zs)))
with self.lock:
if not self.chunks:
return None
xs = [pos[0] for pos in self.chunks]
zs = [pos[1] for pos in self.chunks]
ys = [section_y for chunk in self.chunks.values()
for section_y in chunk.sections]
return ((min(xs), min(ys), min(zs)),
(max(xs), max(ys), max(zs)))
def check_loaded(self, chunk_distance=None):
if self.center is None:
return bool(self.chunks)
radius = self.radius if chunk_distance is None else chunk_distance
if radius is None:
return bool(self.chunks)
cx, cz = self.center
return all((x, z) in self.chunks
for x in range(cx - radius, cx + radius + 1)
for z in range(cz - radius, cz + radius + 1))
with self.lock:
if self.center is None:
return bool(self.chunks)
radius = self.radius if chunk_distance is None else chunk_distance
if radius is None:
return bool(self.chunks)
cx, cz = self.center
return all((x, z) in self.chunks
for x in range(cx - radius, cx + radius + 1)
for z in range(cz - radius, cz + radius + 1))
@property
def loaded_chunk_count(self):
return len(self.chunks)
with self.lock:
return len(self.chunks)
def __repr__(self):
return ('WorldState(dimension={!r}, chunks={}, min_y={}, center={!r}, '
+12 -4
View File
@@ -60,7 +60,11 @@ class World:
def find_signs(self, center, distance=16):
"""Return nearby loaded signs as position/side/four-lines records."""
result = []
for position, entity in self.g.chunks.block_entities.items():
chunks = self.g.chunks
entities = (chunks.block_entities_snapshot()
if hasattr(chunks, 'block_entities_snapshot') else
dict(chunks.block_entities))
for position, entity in entities.items():
if utils.phyp(center, position) > distance:
continue
get_block_at = getattr(self.g.chunks, 'get_block_at', None)
@@ -348,9 +352,13 @@ class World:
yield check
def find_blocks_indexed(self, center, block_ids, distance=0):
index = []
for bid in block_ids:
index.extend(self.g.chunks.index.get(bid, []))
chunks = self.g.chunks
if hasattr(chunks, 'indexed_positions'):
index = chunks.indexed_positions(block_ids)
else:
index = []
for bid in block_ids:
index.extend(chunks.index.get(bid, []))
result = []
for block in index: