Skip to content

Commit

Permalink
Merge pull request #313 from Amulet-Team/improve-block-mesh
Browse files Browse the repository at this point in the history
Improve block mesh
  • Loading branch information
gentlegiantJGC authored Oct 27, 2024
2 parents f6635a0 + 9b481f8 commit 7844bec
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 105 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ requires = [
"wheel",
"versioneer",
"pybind11 ~= 2.12",
"amulet_nbt ~= 4.0a3"
"amulet_nbt ~= 4.0a6"
]
build-backend = "setuptools.build_meta"
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ packages = find_namespace:
python_requires = >=3.11
install_requires =
numpy~=2.0
amulet-nbt~=4.0a3
amulet-nbt~=4.0a6
portalocker~=2.4
amulet-leveldb~=1.0b0
platformdirs~=3.1
Expand Down
150 changes: 85 additions & 65 deletions src/amulet/level/abc/_chunk_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,37 +190,37 @@ def exists(self) -> bool:
return self._get_raw_dimension().has_chunk(self.cx, self.cz)

def _preload(self) -> None:
"""Load the chunk data if it has not already been loaded."""
with self._lock.unique():
if not self._chunk_history.has_resource(self._key):
# The history system is not aware of the chunk. Load from the level data
chunk: Chunk
try:
raw_chunk = self._get_raw_dimension().get_raw_chunk(
self.cx, self.cz
)
chunk = self._get_raw_dimension().raw_chunk_to_native_chunk(
raw_chunk,
self.cx,
self.cz,
)
except ChunkDoesNotExist:
self._chunk_history.set_initial_resource(self._key, b"")
except ChunkLoadError as e:
self._chunk_history.set_initial_resource(self._key, pickle.dumps(e))
else:
self._chunk_history.set_initial_resource(
self._key, pickle.dumps(chunk.chunk_id)
)
for component_id, component_data in chunk.serialise_chunk().items():
if component_data is None:
raise RuntimeError(
"Component must not be None when initialising chunk"
)
self._chunk_data_history.set_initial_resource(
b"/".join((bytes(self._key), component_id.encode())),
component_data,
"""
Load the chunk data if it has not already been loaded.
The lock must be acquired in unique mode before calling this.
"""
if not self._chunk_history.has_resource(self._key):
# The history system is not aware of the chunk. Load from the level data
chunk: Chunk
try:
raw_chunk = self._get_raw_dimension().get_raw_chunk(self.cx, self.cz)
chunk = self._get_raw_dimension().raw_chunk_to_native_chunk(
raw_chunk,
self.cx,
self.cz,
)
except ChunkDoesNotExist:
self._chunk_history.set_initial_resource(self._key, b"")
except ChunkLoadError as e:
self._chunk_history.set_initial_resource(self._key, pickle.dumps(e))
else:
self._chunk_history.set_initial_resource(
self._key, pickle.dumps(chunk.chunk_id)
)
for component_id, component_data in chunk.serialise_chunk().items():
if component_data is None:
raise RuntimeError(
"Component must not be None when initialising chunk"
)
self._chunk_data_history.set_initial_resource(
b"/".join((bytes(self._key), component_id.encode())),
component_data,
)

def _get_null_chunk(self) -> ChunkT:
"""Get a null chunk instance used for this chunk.
Expand Down Expand Up @@ -259,8 +259,9 @@ def get(self, components: Iterable[str] | None = None) -> ChunkT:
:param components: None to load all components or an iterable of component strings to load.
:return: A unique copy of the chunk data.
"""
with self._lock.shared():
self._preload()

def get_chunk() -> ChunkT:
nonlocal components
chunk = self._get_null_chunk()
if components is None:
components = chunk.component_ids
Expand All @@ -275,40 +276,53 @@ def get(self, components: Iterable[str] | None = None) -> ChunkT:
chunk.reconstruct_chunk(chunk_components)
return chunk

def _set(self, chunk: ChunkT | None) -> None:
"""Public lock must be acquired before calling this"""
# Block if the chunk is locked in unique mode.
with self._lock.shared():
if self._chunk_history.has_resource(self._key):
# Does not need loading from disk.
return get_chunk()

# Acquire the lock in unique mode.
with self._lock.unique():
history = self._chunk_history
if not history.has_resource(self._key):
if self._l.history_enabled:
self._preload()
else:
history.set_initial_resource(self._key, b"")
if chunk is None:
history.set_resource(self._key, b"")
# If it wasn't already loaded by another thread.
if not self._chunk_history.has_resource(self._key):
# Load it from disk.
self._preload()

with self._lock.shared():
# If it was loaded in another thread just read it from the cache.
return get_chunk()

def _set(self, chunk: ChunkT | None) -> None:
"""lock must be acquired in unique mode before calling this."""
history = self._chunk_history
if not history.has_resource(self._key):
if self._l.history_enabled:
self._preload()
else:
self._validate_chunk(chunk)
try:
old_chunk_class = self.get_class()
except ChunkLoadError:
old_chunk_class = None
new_chunk_class = type(chunk)
component_data = chunk.serialise_chunk()
if (
old_chunk_class != new_chunk_class
and None in component_data.values()
):
raise RuntimeError(
"When changing chunk class all the data must be present."
)
history.set_resource(self._key, pickle.dumps(new_chunk_class))
for component_id, data in component_data.items():
if data is None:
continue
self._chunk_data_history.set_resource(
b"/".join((bytes(self._key), component_id.encode())),
data,
)
history.set_initial_resource(self._key, b"")
if chunk is None:
history.set_resource(self._key, b"")
else:
self._validate_chunk(chunk)
try:
old_chunk_class = self.get_class()
except ChunkLoadError:
old_chunk_class = None
new_chunk_class = type(chunk)
component_data = chunk.serialise_chunk()
if old_chunk_class != new_chunk_class and None in component_data.values():
raise RuntimeError(
"When changing chunk class all the data must be present."
)
history.set_resource(self._key, pickle.dumps(new_chunk_class))
for component_id, data in component_data.items():
if data is None:
continue
self._chunk_data_history.set_resource(
b"/".join((bytes(self._key), component_id.encode())),
data,
)

@staticmethod
@abstractmethod
Expand All @@ -331,7 +345,13 @@ def set(self, chunk: ChunkT) -> None:
self._l.changed.emit()

def delete(self) -> None:
"""Delete the chunk from the level."""
"""
Delete the chunk from the level.
You must acquire the chunk lock before deleting.
:raises:
LockNotAcquired: If the chunk is already locked by another thread.
"""
with self._lock.unique(blocking=False):
self._set(None)
self.changed.emit()
Expand Down
2 changes: 1 addition & 1 deletion src/amulet/mesh/block/block_mesh.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class BlockMeshPart {
}
};

enum class BlockMeshTransparency {
enum class BlockMeshTransparency : std::uint8_t {
// The block is a full block with opaque textures
FullOpaque,
// The block is a full block with transparent / translucent textures
Expand Down
34 changes: 17 additions & 17 deletions src/amulet/mesh/block/block_mesh.py.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ void init_block_mesh(py::module m_parent)
float>(),
py::arg("x"),
py::arg("y"));
FloatVec2.def_readwrite("x", &Amulet::FloatVec2::x);
FloatVec2.def_readwrite("y", &Amulet::FloatVec2::y);
FloatVec2.def_readonly("x", &Amulet::FloatVec2::x);
FloatVec2.def_readonly("y", &Amulet::FloatVec2::y);

// FloatVec3
py::class_<Amulet::FloatVec3> FloatVec3(m, "FloatVec3",
Expand All @@ -39,9 +39,9 @@ void init_block_mesh(py::module m_parent)
py::arg("x"),
py::arg("y"),
py::arg("z"));
FloatVec3.def_readwrite("x", &Amulet::FloatVec3::x);
FloatVec3.def_readwrite("y", &Amulet::FloatVec3::y);
FloatVec3.def_readwrite("z", &Amulet::FloatVec3::z);
FloatVec3.def_readonly("x", &Amulet::FloatVec3::x);
FloatVec3.def_readonly("y", &Amulet::FloatVec3::y);
FloatVec3.def_readonly("z", &Amulet::FloatVec3::z);

// Vertex
py::class_<Amulet::Vertex> Vertex(m, "Vertex",
Expand All @@ -54,9 +54,9 @@ void init_block_mesh(py::module m_parent)
py::arg("coord"),
py::arg("texture_coord"),
py::arg("tint"));
Vertex.def_readwrite("coord", &Amulet::Vertex::coord, py::doc("The spatial coordinate of the vertex."));
Vertex.def_readwrite("texture_coord", &Amulet::Vertex::texture_coord, py::doc("The texture coordinate of the vertex."));
Vertex.def_readwrite("tint", &Amulet::Vertex::tint, py::doc("The tint colour for the vertex."));
Vertex.def_readonly("coord", &Amulet::Vertex::coord, py::doc("The spatial coordinate of the vertex."));
Vertex.def_readonly("texture_coord", &Amulet::Vertex::texture_coord, py::doc("The texture coordinate of the vertex."));
Vertex.def_readonly("tint", &Amulet::Vertex::tint, py::doc("The tint colour for the vertex."));

// Triangle
py::class_<Amulet::Triangle> Triangle(m, "Triangle",
Expand All @@ -71,10 +71,10 @@ void init_block_mesh(py::module m_parent)
py::arg("vert_index_b"),
py::arg("vert_index_c"),
py::arg("texture_index"));
Triangle.def_readwrite("vert_index_a", &Amulet::Triangle::vert_index_a);
Triangle.def_readwrite("vert_index_b", &Amulet::Triangle::vert_index_b);
Triangle.def_readwrite("vert_index_c", &Amulet::Triangle::vert_index_c);
Triangle.def_readwrite("texture_index", &Amulet::Triangle::texture_index);
Triangle.def_readonly("vert_index_a", &Amulet::Triangle::vert_index_a);
Triangle.def_readonly("vert_index_b", &Amulet::Triangle::vert_index_b);
Triangle.def_readonly("vert_index_c", &Amulet::Triangle::vert_index_c);
Triangle.def_readonly("texture_index", &Amulet::Triangle::texture_index);

// BlockMeshPart
py::class_<Amulet::BlockMeshPart> BlockMeshPart(m, "BlockMeshPart",
Expand All @@ -85,8 +85,8 @@ void init_block_mesh(py::module m_parent)
const std::vector<Amulet::Triangle>&>(),
py::arg("verts"),
py::arg("triangles"));
BlockMeshPart.def_readwrite("verts", &Amulet::BlockMeshPart::verts, py::doc("The vertices in this block mesh part."));
BlockMeshPart.def_readwrite("triangles", &Amulet::BlockMeshPart::triangles, py::doc("The triangles in this block mesh part."));
BlockMeshPart.def_readonly("verts", &Amulet::BlockMeshPart::verts, py::doc("The vertices in this block mesh part."));
BlockMeshPart.def_readonly("triangles", &Amulet::BlockMeshPart::triangles, py::doc("The triangles in this block mesh part."));

// BlockMeshTransparency
py::enum_<Amulet::BlockMeshTransparency>(m, "BlockMeshTransparency",
Expand Down Expand Up @@ -132,9 +132,9 @@ void init_block_mesh(py::module m_parent)
py::arg("transparency"),
py::arg("textures"),
py::arg("parts"));
BlockMesh.def_readwrite("transparency", &Amulet::BlockMesh::transparency, py::doc("The transparency state of this block mesh."));
BlockMesh.def_readwrite("textures", &Amulet::BlockMesh::textures, py::doc("The texture paths used in this block mesh. The Triangle's texture_index attribute is an index into this list."));
BlockMesh.def_readwrite("parts", &Amulet::BlockMesh::parts, py::doc("The mesh parts that make up this mesh. The index corrosponds to the value of BlockMeshCullDirection."));
BlockMesh.def_readonly("transparency", &Amulet::BlockMesh::transparency, py::doc("The transparency state of this block mesh."));
BlockMesh.def_readonly("textures", &Amulet::BlockMesh::textures, py::doc("The texture paths used in this block mesh. The Triangle's texture_index attribute is an index into this list."));
BlockMesh.def_readonly("parts", &Amulet::BlockMesh::parts, py::doc("The mesh parts that make up this mesh. The index corrosponds to the value of BlockMeshCullDirection."));
BlockMesh.def("rotate", &Amulet::BlockMesh::rotate, py::arg("rotx"), py::arg("roty"), py::doc("Rotate the mesh in the x and y axis. Accepted values are -3 to 3 which corrospond to 90 degree rotations."));

m.def(
Expand Down
3 changes: 1 addition & 2 deletions src/amulet/resource_pack/abc/resource_pack_manager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from typing import Optional, Iterator, TypeVar, Generic
import json
import copy

from amulet.block import Block, BlockStack
from amulet.mesh.block import BlockMesh, merge_block_meshes, get_missing_block
Expand Down Expand Up @@ -80,7 +79,7 @@ def get_block_model(self, block_stack: BlockStack) -> BlockMesh:
)
)

return copy.deepcopy(self._cached_models[block_stack])
return self._cached_models[block_stack]

def _get_model(self, block: Block) -> BlockMesh:
raise NotImplementedError
Loading

0 comments on commit 7844bec

Please sign in to comment.