Synchronize world chunk index updates
This commit is contained in:
+123
-96
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user