Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-11-17 08:30:04 +01:00
Initial work on new disk clipboard
Dieser Commit ist enthalten in:
Ursprung
da4d966d9e
Commit
a05c607549
@ -0,0 +1,39 @@
|
|||||||
|
package com.fastasyncworldedit.core.extent.clipboard;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.IFawe;
|
||||||
|
import com.sk89q.worldedit.extension.platform.Actor;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class Clipboards {
|
||||||
|
|
||||||
|
public static Clipboard create(Region region, BlockVector3 origin, Actor actor) {
|
||||||
|
if (!(region instanceof CuboidRegion)) {
|
||||||
|
return new BlockArrayClipboard(region, actor.getUniqueId());
|
||||||
|
}
|
||||||
|
return new DiskBasedClipboard(region.getDimensions(), region.getMinimumPoint(), origin, createActorPath(actor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path createActorPath(Actor actor) {
|
||||||
|
Path folder = Objects.requireNonNull(Fawe.<IFawe>platform(), "Platform not present")
|
||||||
|
.getDirectory().toPath()
|
||||||
|
.resolve("clipboards")
|
||||||
|
.resolve(actor.getUniqueId().toString());
|
||||||
|
try {
|
||||||
|
Files.createDirectories(folder);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,282 @@
|
|||||||
|
package com.fastasyncworldedit.core.extent.clipboard;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.util.io.MemoryFile;
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
|
import com.sk89q.worldedit.entity.Entity;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
import com.sk89q.worldedit.util.nbt.BinaryTagIO;
|
||||||
|
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
|
||||||
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
|
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||||
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class DiskBasedClipboard implements Clipboard {
|
||||||
|
|
||||||
|
private static final int INVALID_INDEX = -1;
|
||||||
|
private static final BlockState AIR = Objects.requireNonNull(BlockTypes.AIR).getDefaultState();
|
||||||
|
private static final BiomeType OCEAN = BiomeTypes.OCEAN;
|
||||||
|
|
||||||
|
private final MemoryFile blockFile;
|
||||||
|
private MemoryFile biomeFile;
|
||||||
|
|
||||||
|
private final BlockVector3 dimensions;
|
||||||
|
private final BlockVector3 offset;
|
||||||
|
private final Path folder;
|
||||||
|
private final Int2ReferenceMap<CompoundBinaryTag> nbt = new Int2ReferenceOpenHashMap<>();
|
||||||
|
private BlockVector3 origin;
|
||||||
|
|
||||||
|
DiskBasedClipboard(
|
||||||
|
final BlockVector3 dimensions,
|
||||||
|
final BlockVector3 offset,
|
||||||
|
final BlockVector3 origin,
|
||||||
|
final Path folder
|
||||||
|
) {
|
||||||
|
this.dimensions = dimensions;
|
||||||
|
this.offset = offset;
|
||||||
|
this.origin = origin;
|
||||||
|
this.folder = folder;
|
||||||
|
long entries = requiredEntries(dimensions);
|
||||||
|
try {
|
||||||
|
this.blockFile = MemoryFile.create(folder.resolve("blocks.dc"), entries, BlockTypesCache.states.length);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long requiredEntries(BlockVector3 dimensions) {
|
||||||
|
return (long) dimensions.getX() * dimensions.getY() * dimensions.getZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long requiredBiomeEntries(BlockVector3 dimensions) {
|
||||||
|
return requiredEntries(dimensions.divide(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseBlock getFullBlock(final int x, final int y, final int z) {
|
||||||
|
final int index = toLocalIndex(x, y, z);
|
||||||
|
if (index != INVALID_INDEX) {
|
||||||
|
BlockState state = getBlock(index);
|
||||||
|
CompoundBinaryTag tag = this.nbt.get(index);
|
||||||
|
return state.toBaseBlock(tag); // passing null is fine
|
||||||
|
}
|
||||||
|
return AIR.toBaseBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiomeType getBiomeType(final int x, final int y, final int z) {
|
||||||
|
MemoryFile memoryFile = ensureBiomeFile();
|
||||||
|
final int index = toLocalBiomeIndex(x, y, z);
|
||||||
|
if (index != INVALID_INDEX) {
|
||||||
|
return BiomeTypes.get(memoryFile.getValue(index));
|
||||||
|
}
|
||||||
|
return OCEAN; // as per documentation in InputExtent
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState getBlock(final int x, final int y, final int z) {
|
||||||
|
final int index = toLocalIndex(x, y, z);
|
||||||
|
if (index != INVALID_INDEX) {
|
||||||
|
return getBlock(index);
|
||||||
|
}
|
||||||
|
return AIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <B extends BlockStateHolder<B>> boolean setBlock(final int x, final int y, final int z, final B block) throws
|
||||||
|
WorldEditException {
|
||||||
|
final int index = toLocalIndex(x, y, z);
|
||||||
|
if (index != INVALID_INDEX) {
|
||||||
|
this.blockFile.setValue(index, getId(block));
|
||||||
|
if (block instanceof BaseBlock bb) {
|
||||||
|
dealWithNbt(bb, index);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dealWithNbt(BaseBlock bb, int index) {
|
||||||
|
final CompoundBinaryTag value = bb.getNbt();
|
||||||
|
if (value != null) {
|
||||||
|
nbt.put(index, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public boolean setTile(final int x, final int y, final int z, final CompoundTag tile) throws WorldEditException {
|
||||||
|
final int index = toLocalIndex(x, y, z);
|
||||||
|
if (index != INVALID_INDEX) {
|
||||||
|
this.nbt.put(index, tile.asBinaryTag());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean fullySupports3DBiomes() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setBiome(final int x, final int y, final int z, final BiomeType biome) {
|
||||||
|
MemoryFile biomeFile = ensureBiomeFile();
|
||||||
|
final int index = toLocalBiomeIndex(x, y, z);
|
||||||
|
if (index != INVALID_INDEX) {
|
||||||
|
biomeFile.setValue(index, biome.getInternalId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MemoryFile ensureBiomeFile() {
|
||||||
|
MemoryFile biomeFile = this.biomeFile;
|
||||||
|
if (biomeFile != null) {
|
||||||
|
return biomeFile;
|
||||||
|
}
|
||||||
|
int biomeCount = BiomeTypes.getMaxId();
|
||||||
|
try {
|
||||||
|
biomeFile = MemoryFile.create(folder.resolve("biomes.dc"), requiredBiomeEntries(this.dimensions), biomeCount);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
this.biomeFile = biomeFile;
|
||||||
|
return biomeFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Region getRegion() {
|
||||||
|
return new CuboidRegion(getMinimumPoint(), getMaximumPoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockVector3 getDimensions() {
|
||||||
|
return this.dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockVector3 getOrigin() {
|
||||||
|
return this.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOrigin(final BlockVector3 origin) {
|
||||||
|
this.origin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasBiomes() {
|
||||||
|
return this.biomeFile != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeEntity(final Entity entity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
this.blockFile.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
try {
|
||||||
|
this.blockFile.flush();
|
||||||
|
writeNbt();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeNbt() throws IOException {
|
||||||
|
if (this.nbt.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, CompoundBinaryTag> converted = new HashMap<>();
|
||||||
|
for (final Int2ReferenceMap.Entry<CompoundBinaryTag> entry : this.nbt.int2ReferenceEntrySet()) {
|
||||||
|
converted.put(String.valueOf(entry.getIntKey()), entry.getValue());
|
||||||
|
}
|
||||||
|
CompoundBinaryTag tiles = CompoundBinaryTag.from(converted);
|
||||||
|
BinaryTagIO.writer().write(tiles, this.folder.resolve("entities.nbt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockVector3 getMinimumPoint() {
|
||||||
|
return this.offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockVector3 getMaximumPoint() {
|
||||||
|
return this.offset.add(this.dimensions.subtract(1, 1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean inRegion(int x, int y, int z) {
|
||||||
|
return (x | y | z) >= 0 && y < this.dimensions.getY() && x < this.dimensions.getX() && z < this.dimensions.getZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private <B extends BlockStateHolder<B>> int getId(B b) {
|
||||||
|
return b.getOrdinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int toLocalIndex(int x, int y, int z) {
|
||||||
|
int lx = x - this.offset.getX();
|
||||||
|
int ly = y - this.offset.getY();
|
||||||
|
int lz = z - this.offset.getZ();
|
||||||
|
if (inRegion(lx, ly, lz)) {
|
||||||
|
return toIndexUnchecked(lx, ly, lz);
|
||||||
|
}
|
||||||
|
return INVALID_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// l = local
|
||||||
|
private int toIndexUnchecked(int lx, int ly, int lz) {
|
||||||
|
// chosen to correspond to iteration order in CuboidRegion#iterator()
|
||||||
|
// to minimize cache misses/page faults
|
||||||
|
return lx + this.dimensions.getX() * (lz + ly * this.dimensions.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int toLocalBiomeIndex(int x, int y, int z) {
|
||||||
|
int lx = x - this.offset.getX();
|
||||||
|
int ly = y - this.offset.getY();
|
||||||
|
int lz = z - this.offset.getZ();
|
||||||
|
if (inRegion(lx, ly, lz)) {
|
||||||
|
return toBiomeIndexUnchecked(lx >> 2, ly >> 2, lz >> 2);
|
||||||
|
}
|
||||||
|
return INVALID_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// b = biome, l = local
|
||||||
|
private int toBiomeIndexUnchecked(int blx, int bly, int blz) {
|
||||||
|
// chosen to correspond to iteration order in CuboidRegion#iterator()
|
||||||
|
// to minimize cache misses/page faults
|
||||||
|
return blx + (this.dimensions.getX() >> 2) * (blz + bly * (this.dimensions.getZ() >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockState getBlock(final int i) {
|
||||||
|
return BlockTypesCache.states[this.blockFile.getValue(i)];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.fastasyncworldedit.core.util.io;
|
||||||
|
|
||||||
|
import java.io.Flushable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
|
||||||
|
public sealed interface MemoryFile extends AutoCloseable, Flushable permits SmallMemoryFile {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return a memory-mapped file that can store up to {@code entries} integers in the range of {@code [0, valueCount)}}
|
||||||
|
*/
|
||||||
|
static MemoryFile create(Path file, long entries, int valueCount) throws IOException {
|
||||||
|
int bitsPerEntry = MemoryFileSupport.bitsPerEntry(valueCount);
|
||||||
|
long bytesNeeded = MemoryFileSupport.requiredBytes(bitsPerEntry, entries);
|
||||||
|
if (bytesNeeded <= Integer.MAX_VALUE) {
|
||||||
|
return new SmallMemoryFile(FileChannel.open(file, MemoryFileSupport.OPTIONS), (int) bytesNeeded, bitsPerEntry);
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("too many entries: " + entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setValue(int index, int value);
|
||||||
|
|
||||||
|
int getValue(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
void close() throws IOException;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.fastasyncworldedit.core.util.io;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.util.MathMan;
|
||||||
|
import org.jetbrains.annotations.Range;
|
||||||
|
|
||||||
|
import java.nio.file.OpenOption;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
final class MemoryFileSupport {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of additional bytes required to safely call {@link java.nio.ByteBuffer#getInt(int)}
|
||||||
|
* and {@link java.nio.ByteBuffer#putInt(int, int)} for the last actually used byte.
|
||||||
|
*/
|
||||||
|
static final int PADDING = 3;
|
||||||
|
static final Set<? extends OpenOption> OPTIONS = EnumSet.of(
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.WRITE,
|
||||||
|
StandardOpenOption.READ
|
||||||
|
);
|
||||||
|
|
||||||
|
static long requiredBytes(int bitsPerEntry, long entries) {
|
||||||
|
long bitsNeeded = bitsPerEntry * entries;
|
||||||
|
// Math.ceilDiv is Java 18+
|
||||||
|
return -Math.floorDiv(-bitsNeeded, 8) + MemoryFileSupport.PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bitsPerEntry(int valueCount) {
|
||||||
|
if (Integer.highestOneBit(valueCount) == Integer.lowestOneBit(valueCount)) {
|
||||||
|
return Integer.numberOfTrailingZeros(valueCount);
|
||||||
|
}
|
||||||
|
return MathMan.log2nlz(valueCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the shift required for this bitPos
|
||||||
|
static @Range(from = 0, to = 7) int shift(long bitPos, long bytePos) {
|
||||||
|
return (int) (bitPos - (bytePos << 3));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package com.fastasyncworldedit.core.util.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.MappedByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
|
||||||
|
import static com.fastasyncworldedit.core.util.io.MemoryFileSupport.shift;
|
||||||
|
|
||||||
|
final class SmallMemoryFile implements MemoryFile {
|
||||||
|
private final FileChannel channel;
|
||||||
|
private final MappedByteBuffer buffer;
|
||||||
|
private final int bitsPerEntry;
|
||||||
|
private final int entryMask;
|
||||||
|
|
||||||
|
SmallMemoryFile(FileChannel channel, int size, final int bitsPerEntry) throws IOException {
|
||||||
|
this.channel = channel;
|
||||||
|
this.buffer = this.channel.map(FileChannel.MapMode.READ_WRITE, 0, size);
|
||||||
|
this.buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
this.bitsPerEntry = bitsPerEntry;
|
||||||
|
this.entryMask = (1 << this.bitsPerEntry) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(int index, int value) {
|
||||||
|
long bitPos = bitPos(index);
|
||||||
|
int bytePos = toBytePos(bitPos);
|
||||||
|
int shift = shift(bitPos, bytePos);
|
||||||
|
write(bytePos, shift, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getValue(final int index) {
|
||||||
|
long bitPos = bitPos(index);
|
||||||
|
int bytePos = toBytePos(bitPos);
|
||||||
|
int shift = shift(bitPos, bytePos);
|
||||||
|
return read(bytePos, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(int bytePos, int shift, int value) {
|
||||||
|
int mask = this.entryMask << shift;
|
||||||
|
int existing = this.buffer.getInt(bytePos);
|
||||||
|
int result = (existing & ~mask) | (value << shift);
|
||||||
|
this.buffer.putInt(bytePos, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int read(int bytePos, int shift) {
|
||||||
|
return (this.buffer.getInt(bytePos) >> shift) & this.entryMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int toBytePos(long bitPos) {
|
||||||
|
return (int) (bitPos >> 3); // must be in int range for SmallMemoryFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private long bitPos(int index) {
|
||||||
|
return (long) this.bitsPerEntry * index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
flush();
|
||||||
|
this.channel.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
this.buffer.force();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -24,6 +24,7 @@ import com.fastasyncworldedit.core.FaweCache;
|
|||||||
import com.fastasyncworldedit.core.configuration.Caption;
|
import com.fastasyncworldedit.core.configuration.Caption;
|
||||||
import com.fastasyncworldedit.core.configuration.Settings;
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
import com.fastasyncworldedit.core.event.extent.PasteEvent;
|
import com.fastasyncworldedit.core.event.extent.PasteEvent;
|
||||||
|
import com.fastasyncworldedit.core.extent.clipboard.Clipboards;
|
||||||
import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard;
|
import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard;
|
||||||
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
||||||
import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard;
|
import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard;
|
||||||
@ -154,9 +155,9 @@ public class ClipboardCommands {
|
|||||||
}
|
}
|
||||||
session.setClipboard(null);
|
session.setClipboard(null);
|
||||||
|
|
||||||
Clipboard clipboard = new BlockArrayClipboard(region, actor.getUniqueId());
|
final BlockVector3 origin = centerClipboard ? region.getCenter().toBlockPoint().withY(region.getMinimumY()) :
|
||||||
clipboard.setOrigin(centerClipboard ? region.getCenter().toBlockPoint().withY(region.getMinimumY()) :
|
session.getPlacementPosition(actor);
|
||||||
session.getPlacementPosition(actor));
|
Clipboard clipboard = Clipboards.create(region, origin, actor);
|
||||||
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
|
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
|
||||||
copy.setCopyingEntities(copyEntities);
|
copy.setCopyingEntities(copyEntities);
|
||||||
copy.setCopyingBiomes(copyBiomes);
|
copy.setCopyingBiomes(copyBiomes);
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.fastasyncworldedit.core.util.io;
|
||||||
|
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class MemoryFileSupportTest {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource(textBlock = """
|
||||||
|
1, 2
|
||||||
|
2, 3
|
||||||
|
2, 4
|
||||||
|
3, 5
|
||||||
|
8, 255
|
||||||
|
8, 256
|
||||||
|
9, 257
|
||||||
|
""")
|
||||||
|
void testBitsPerEntry(int expected, int entries) {
|
||||||
|
assertEquals(expected, MemoryFileSupport.bitsPerEntry(entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.fastasyncworldedit.core.util.io;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
|
class MemoryFileTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeALot(@TempDir Path dir) throws IOException {
|
||||||
|
Path file = dir.resolve("data.tmp");
|
||||||
|
int entries = Integer.MAX_VALUE - 10;
|
||||||
|
RandomGenerator generator = RandomGenerator.getDefault();
|
||||||
|
try (MemoryFile memoryFile = MemoryFile.create(file, entries, 256)) {
|
||||||
|
for (int i = 0; i < entries; i++) {
|
||||||
|
memoryFile.setValue(i, generator.nextInt(256));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren