Amulet-Core icon indicating copy to clipboard operation
Amulet-Core copied to clipboard

[Feature Request] Improved Threading

Open gentlegiantJGC opened this issue 1 year ago • 0 comments

Feature Request

The Problem

The current implementation of Amulet was not really designed with threading in mind. I think we need to rewrite the API to be more thread safe in an environment where some users are unaware of threads and locks.

Feature Description

Instead of the get and set methods returning the object stored internally they should return a deep copy of the data. This means that when a thread gets a chunk it is the only thread that is modifying that chunk object. When setting the data back it is deep copied again so that the thread does not hold a reference to the internal data. We should also add an edit context manager so that multiple threads can work around each other while editing the same object.

Code

from threading import RLock
from copy import deepcopy
from contextlib import contextmanager
import numpy


class Chunk:
    def __init__(self):
        self.data = numpy.zeros((16, 16, 16))


class Level:
    def __init__(self):
        self._chunk = Chunk()
        self._lock = RLock()

    def get_chunk(self) -> Chunk:
        """
        Get a deep copy of the chunk data.
        If you want to edit the chunk use :meth:`edit_chunk` instead.
        """
        return deepcopy(self._chunk)

    def set_chunk(self, chunk: Chunk):
        """
        Overwrite the chunk data.
        If you want to edit the chunk use :meth:`edit_chunk` instead.

        :param chunk: The chunk data to set.
        """
        self._chunk = deepcopy(chunk)

    @contextmanager
    def edit_chunk(self):
        """
        Lock and edit a chunk.

        >>> with level.edit_chunk() as chunk:
        >>>     # Edit the chunk data
        >>>     # No other threads can edit the chunk while in this with block.
        >>>     # When the with block exits the edited chunk will be automatically set if no exception occurred.
        """
        with self._lock:
            chunk = self.get_chunk()
            yield chunk
            self.set_chunk(chunk)

Additional context

This has effects on Amulet-Team/Amulet-Core#256 and Amulet-Team/Amulet-Core#257 We would also need to think about how the history system would work if two operations were running at the same time but this is a baby step towards that.

gentlegiantJGC avatar Apr 22 '23 11:04 gentlegiantJGC