class Hunk:
    """
    Helper class to wrap low-level VBO objects with data caches, change ranges,
    and flushing of the changed range to the hunk VBO in graphics card RAM.
    """
    def __init__(self, hunkNumber, nVertices, nCoords):
        """
        hunkNumber - The index of this hunk, e.g. 0 for the first in a group.
        Specifies the range of IDs residing in this hunk.
        
        nVertices - The number of vertices in the primitive drawing pattern.

        nCoords - The number of entries per attribute, e.g. 1 for float, 3 for
        vec3, 4 for vec4, so the right size storage can be allocated.
        """
        self.nVertices = nVertices
        self.hunkNumber = hunkNumber
        self.nCoords = nCoords

        self.VBO = GLBufferObject(
            GL_ARRAY_BUFFER_ARB,
            # Per-vertex attributes are all multiples (1-4) of Float32.
            HUNK_SIZE * self.nVertices * self.nCoords * BYTES_PER_FLOAT,
            GL_STATIC_DRAW)

        # Low- and high-water marks to optimize for sending a range of data.
        self.unchanged()
        return

    def unchanged(self):
        """
        Mark a Hunk as unchanged.  (Empty or flushed.) 
        """
        self.low = self.high = 0
        return

    def changedRange(self, chgLowID, chgHighID):
        """
        Record a range of data changes, for later flushing.

        chgLowID and chgHighID are primitive IDs, possibly spanning many hunks.

        The high end is the index of the one *after* the end of the range, as
        usual in Python.
        """
        (lowHunk, lowIndex) = decodePrimID(chgLowID)
        (highHunk, highIndex) = decodePrimID(chgHighID)

        if self.hunkNumber < lowHunk or self.hunkNumber > highHunk:
            return  # This hunk is not in the hunk range.

        if lowHunk < self.hunkNumber:
            self.low = 0
        else:
            self.low = min(self.low, lowIndex)  # Maybe extend the range.
            pass

        if highHunk > self.hunkNumber:
            self.high = HUNK_SIZE
        else:
            self.high = max(self.high, highIndex)  # Maybe extend the range.
            pass

        return

    def flush(self, allData):
        """
        Update a changed range of the data that applies to this hunk, sending it
        to the Buffer Object in graphics card RAM.

        allData - List of data blocks for the whole hunk-list.  We'll extract
        just the part relevant to the changed part of this particular hunk.

        Internally, the data is a list, block-indexed by primitive ID, but
        replicated in block sublists by a factor of self.nVertices to match the
        vertex buffer size.  What reaches the attribute VBO is automatically
        flattened into a sequence.
        """
        rangeSize = self.high - self.low
        assert rangeSize >= 0
        if rangeSize == 0:
            # Nothing to do.
            return

        lowID = (self.hunkNumber * HUNK_SIZE) + self.low
        highID = (self.hunkNumber * HUNK_SIZE) + self.high

        # Send all or part of the Python data to C.
        C_data = numpy.array(allData[lowID:highID], dtype=numpy.float32)

        if rangeSize == HUNK_SIZE:
            # Special case to send the whole Hunk's worth of data.
            self.VBO.updateAll(C_data)
        else:
            # Send a portion of the HunkBuffer, with byte offset within the VBO.
            # (Per-vertex attributes are all multiples (1-4) of Float32.)
            offset = self.low * self.nVertices * self.nCoords * BYTES_PER_FLOAT
            self.VBO.update(offset, C_data)
            pass

        self.unchanged()  # Now we're in sync.
        return

    pass  # End of class HunkBuffer.
class Hunk:
    """
    Helper class to wrap low-level VBO objects with data caches, change ranges,
    and flushing of the changed range to the hunk VBO in graphics card RAM.
    """
    def __init__(self, hunkNumber, nVertices, nCoords):
        """
        hunkNumber - The index of this hunk, e.g. 0 for the first in a group.
        Specifies the range of IDs residing in this hunk.

        nVertices - The number of vertices in the primitive drawing pattern.

        nCoords - The number of entries per attribute, e.g. 1 for float, 3 for
        vec3, 4 for vec4, so the right size storage can be allocated.
        """
        self.nVertices = nVertices
        self.hunkNumber = hunkNumber
        self.nCoords = nCoords

        self.VBO = GLBufferObject(
            GL_ARRAY_BUFFER_ARB,
            # Per-vertex attributes are all multiples (1-4) of Float32.
            HUNK_SIZE * self.nVertices * self.nCoords * BYTES_PER_FLOAT,
            GL_STATIC_DRAW)

        # Low- and high-water marks to optimize for sending a range of data.
        self.unchanged()
        return

    def unchanged(self):
        """
        Mark a Hunk as unchanged.  (Empty or flushed.)
        """
        self.low = self.high = 0
        return

    def changedRange(self, chgLowID, chgHighID):
        """
        Record a range of data changes, for later flushing.

        chgLowID and chgHighID are primitive IDs, possibly spanning many hunks.

        The high end is the index of the one *after* the end of the range, as
        usual in Python.
        """
        (lowHunk, lowIndex) = decodePrimID(chgLowID)
        (highHunk, highIndex) = decodePrimID(chgHighID)

        if self.hunkNumber < lowHunk or self.hunkNumber > highHunk:
            return              # This hunk is not in the hunk range.

        if lowHunk < self.hunkNumber:
            self.low = 0
        else:
            self.low = min(self.low, lowIndex) # Maybe extend the range.
            pass

        if highHunk > self.hunkNumber:
            self.high = HUNK_SIZE
        else:
            self.high = max(self.high, highIndex) # Maybe extend the range.
            pass

        return

    def flush(self, allData):
        """
        Update a changed range of the data that applies to this hunk, sending it
        to the Buffer Object in graphics card RAM.

        allData - List of data blocks for the whole hunk-list.  We'll extract
        just the part relevant to the changed part of this particular hunk.

        Internally, the data is a list, block-indexed by primitive ID, but
        replicated in block sublists by a factor of self.nVertices to match the
        vertex buffer size.  What reaches the attribute VBO is automatically
        flattened into a sequence.
        """
        rangeSize = self.high - self.low
        assert rangeSize >= 0
        if rangeSize == 0:
            # Nothing to do.
            return

        lowID = (self.hunkNumber * HUNK_SIZE) + self.low
        highID = (self.hunkNumber * HUNK_SIZE) + self.high

        # Send all or part of the Python data to C.
        C_data = numpy.array(allData[lowID:highID], dtype=numpy.float32)

        if rangeSize == HUNK_SIZE:
            # Special case to send the whole Hunk's worth of data.
            self.VBO.updateAll(C_data)
        else:
            # Send a portion of the HunkBuffer, with byte offset within the VBO.
            # (Per-vertex attributes are all multiples (1-4) of Float32.)
            offset = self.low * self.nVertices * self.nCoords * BYTES_PER_FLOAT
            self.VBO.update(offset, C_data)
            pass

        self.unchanged()             # Now we're in sync.
        return

    pass # End of class HunkBuffer.