/** * Sets the block id, data and auxData for the block at (x, y, z).<br> * <br> * If the data is 0 and the auxData is null, then the block will be stored as a single short.<br> * * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @param id the block id * @param data the block data * @param auxData the block auxiliary data */ public final void setBlock(int x, int y, int z, short id, short data, T auxData) { int index = getIndex(x, y, z); int spins = 0; boolean interrupted = false; try { while (true) { if (spins++ > SPINS) { interrupted |= atomicWait(); } checkCompressing(); short oldBlockId = blockIds.get(index); boolean oldReserved = auxStore.isReserved(oldBlockId); if (data == 0 && auxData == null && !auxStore.isReserved(id)) { if (!blockIds.compareAndSet(index, oldBlockId, id)) { continue; } if (oldReserved) { if (!auxStore.remove(oldBlockId)) { throw new IllegalStateException( "setBlock() tried to remove old record, but it had already been removed"); } } return; } else { int newIndex = auxStore.add(id, data, auxData); if (!blockIds.compareAndSet(index, oldBlockId, (short) newIndex)) { if (auxStore.remove(newIndex)) { throw new IllegalStateException( "setBlock() tried to remove old record, but it had already been removed"); } continue; } if (oldReserved) { if (!auxStore.remove(oldBlockId)) { throw new IllegalStateException( "setBlock() tried to remove old record, but it had already been removed"); } } return; } } } finally { markDirty(x, y, z); atomicNotify(); if (interrupted) { Thread.currentThread().interrupt(); } } }
/** * Gets the sequence number associated with a block location.<br> * <br> * If soft is true, this method counts as a volatile read. Otherwise, it is both a volatile read * and a volatile write.<br> * <br> * Soft reads should only be used for the first of the 2 step process for confirming that data * hasn't changed. * * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @return the sequence number, or DatatableSequenceNumber.ATOMIC for a single short record */ public final int getSequence(int x, int y, int z) { checkCompressing(); int index = getIndex(x, y, z); int spins = 0; boolean interrupted = false; try { while (true) { if (spins++ > SPINS) { interrupted |= atomicWait(); } checkCompressing(); int blockId = blockIds.get(index); if (!auxStore.isReserved(blockId)) { return DatatableSequenceNumber.ATOMIC; } else { int sequence = auxStore.getSequence(blockId); if (sequence != DatatableSequenceNumber.UNSTABLE) { return sequence; } } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } }
/** * Gets the block auxiliary data for a block at a particular location.<br> * <br> * Block data ranges from 0 to 65535. * * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @return the block auxiliary data */ public final T getAuxData(int x, int y, int z) { int index = getIndex(x, y, z); int spins = 0; boolean interrupted = false; try { while (true) { if (spins++ > SPINS) { interrupted |= atomicWait(); } checkCompressing(); int seq = getSequence(x, y, z); short blockId = blockIds.get(index); if (auxStore.isReserved(blockId)) { T auxData = auxStore.getAuxData(blockId); if (testSequence(x, y, z, seq)) { return auxData; } } else { return null; } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } }
/** * Copies the block data in the store into an array.<br> * <br> * If the store is updated while this snapshot is being taken, data tearing could occur.<br> * <br> * If the array is the wrong length or null, a new array is created. * * @param the array to place the data * @return the array */ public short[] getDataArray(short[] array) { int length = blockIds.length(); if (array == null || array.length != length) { array = new short[length]; } for (int i = 0; i < length; i++) { short blockId = blockIds.get(i); if (auxStore.isReserved(blockId)) { array[i] = auxStore.getData(blockId); } else { array[i] = 0; } } return array; }
/** * Copies the block ids in the store into an array.<br> * <br> * If the store is updated while this snapshot is being taken, data tearing could occur.<br> * <br> * If the array is the wrong length or null, a new array is created. * * @param the array to place the data * @return the array */ public short[] getBlockIdArray(short[] array) { int length = blockIds.length(); if (array == null || array.length != length) { array = new short[length]; } for (int i = 0; i < length; i++) { short blockId = blockIds.get(i); if (auxStore.isReserved(blockId)) { blockId = auxStore.getId(blockId); } else { blockId &= 0x0000FFFF; } array[i] = blockId; } return array; }
/** * Compresses the auxiliary store.<br> * <br> * This method should only be called when the store is guaranteed not to be accessed from any * other thread.<br> */ public final void compress() { if (!compressing.compareAndSet(false, true)) { throw new IllegalStateException("Compression started while compression was in progress"); } int length = side * side * side; AtomicIntReferenceArrayStore<T> newAuxStore = new AtomicIntReferenceArrayStore<T>(side * side * side); for (int i = 0; i < length; i++) { short blockId = blockIds.get(i); if (auxStore.isReserved(blockId)) { short storedId = auxStore.getId(blockId); short storedData = auxStore.getData(blockId); T storedAuxData = auxStore.getAuxData(blockId); int newIndex = newAuxStore.add(storedId, storedData, storedAuxData); if (!blockIds.compareAndSet(i, blockId, (short) newIndex)) { throw new IllegalStateException("Unstable block id data during compression step"); } } } auxStore = newAuxStore; compressing.set(false); }
/** * Atomically gets the full set of data associated with the block.<br> * <br> * * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @param fullState a BlockFullState object to store the return value, or null to generate a new * one * @return the full state of the block */ public final BlockFullState<T> getFullData(int x, int y, int z, BlockFullState<T> fullData) { if (fullData == null) { fullData = new BlockFullState<T>(); } int index = getIndex(x, y, z); int spins = 0; boolean interrupted = false; try { while (true) { if (spins++ > SPINS) { interrupted |= atomicWait(); } checkCompressing(); int seq = getSequence(x, y, z); short blockId = blockIds.get(index); if (auxStore.isReserved(blockId)) { fullData.setId(auxStore.getId(blockId)); fullData.setData(auxStore.getData(blockId)); fullData.setAuxData(auxStore.getAuxData(blockId)); if (testSequence(x, y, z, seq)) { return fullData; } } else { fullData.setId(blockId); fullData.setData((short) 0); fullData.setAuxData(null); return fullData; } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } }
/** * Tests if a the sequence number associated with a particular block location has not changed. * * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @param expected the expected sequence number * @return true if the sequence number has not changed and expected is not * DatatableSequenceNumber.ATOMIC */ public final boolean testSequence(int x, int y, int z, int expected) { if (expected == DatatableSequenceNumber.ATOMIC) { return false; } checkCompressing(); int index = getIndex(x, y, z); int spins = 0; boolean interrupted = false; try { if (spins++ > SPINS) { interrupted |= atomicWait(); } checkCompressing(); int blockId = blockIds.get(index); return auxStore.isReserved(blockId) && auxStore.testSequence(blockId, expected); } finally { if (interrupted) { Thread.currentThread().interrupt(); } } }
/** * Sets the block id, data and auxData for the block at (x, y, z), if the current data matches the * expected data.<br> * * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @param expectId the expected block id * @param expectData the expected block data * @param expectAuxData the expected block auxiliary data * @param newId the new block id * @param newData the new block data * @param newAuxData the new block auxiliary data * @return true if the block was set */ public final boolean compareAndSetBlock( int x, int y, int z, short expectId, short expectData, T expectAuxData, short newId, short newData, T newAuxData) { int index = getIndex(x, y, z); int spins = 0; boolean interrupted = false; try { while (true) { if (spins++ > SPINS) { interrupted |= atomicWait(); } checkCompressing(); short oldBlockId = blockIds.get(index); boolean oldReserved = auxStore.isReserved(oldBlockId); if (!oldReserved) { if (blockIds.get(index) != expectId || expectData != 0 || expectAuxData != null) { return false; } } else { int seq = auxStore.getSequence(oldBlockId); short oldId = auxStore.getId(oldBlockId); short oldData = auxStore.getData(oldBlockId); T oldAuxData = auxStore.getAuxData(oldBlockId); if (!testSequence(x, y, z, seq)) { continue; } if (oldId != expectId || oldData != expectData || oldAuxData != expectAuxData) { return false; } } if (newData == 0 && newAuxData == null && !auxStore.isReserved(newId)) { if (!blockIds.compareAndSet(index, oldBlockId, newId)) { continue; } if (oldReserved) { if (!auxStore.remove(oldBlockId)) { throw new IllegalStateException( "setBlock() tried to remove old record, but it had already been removed"); } } markDirty(x, y, z); return true; } else { int newIndex = auxStore.add(newId, newData, newAuxData); if (!blockIds.compareAndSet(index, oldBlockId, (short) newIndex)) { if (!auxStore.remove(newIndex)) { throw new IllegalStateException( "setBlock() tried to remove old record, but it had already been removed"); } continue; } if (oldReserved) { if (!auxStore.remove(oldBlockId)) { throw new IllegalStateException( "setBlock() tried to remove old record, but it had already been removed"); } } markDirty(x, y, z); return true; } } } finally { atomicNotify(); if (interrupted) { Thread.currentThread().interrupt(); } } }