diff --git a/README.md b/README.md index d690f08c..43a40a81 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,23 @@ -# Overview -Optimize worldedit -# Main resource page -https://www.spigotmc.org/resources/13932/ \ No newline at end of file +# About +FAWE is an addon for WorldEdit that has huge speed and memory improvements as well as a few extra features. + +# Spigot page +https://www.spigotmc.org/resources/13932/ + +# IRC +http://webchat.esper.net/?nick=&channels=IntellectualCrafters + +# Releases: +https://github.com/boy0001/FastAsyncWorldedit/releases + +# Building +> FAWE uses gradle to build + +gradlew setupDecompWorkspace +gradlew build + +# Contributing +Have an idea for an optimization, or a cool feature? + - I'll accept most PR's + - Let me know what you've tested / what may need further testing + - If you need any help, create a ticket or discuss on IRC \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index 44b70f8f..7da5ce81 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -22,6 +22,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.ClipboardCommands; import com.sk89q.worldedit.command.SchematicCommands; import com.sk89q.worldedit.command.ScriptingCommands; import com.sk89q.worldedit.extension.platform.CommandManager; @@ -155,6 +156,7 @@ public class Fawe { */ this.setupConfigs(); MainUtil.deleteOlder(new File(IMP.getDirectory(), "history"), TimeUnit.DAYS.toMillis(Settings.DELETE_HISTORY_AFTER_DAYS)); + MainUtil.deleteOlder(new File(IMP.getDirectory(), "clipboard"), TimeUnit.DAYS.toMillis(Settings.DELETE_CLIPBOARD_AFTER_DAYS)); TaskManager.IMP = this.IMP.getTaskManager(); if (Settings.METRICS) { @@ -221,6 +223,7 @@ public class Fawe { private void setupInjector() { EditSession.inject(); Operations.inject(); + ClipboardCommands.inject(); SchematicCommands.inject(); ScriptingCommands.inject(); BreadthFirstSearch.inject(); diff --git a/core/src/main/java/com/boydti/fawe/config/BBC.java b/core/src/main/java/com/boydti/fawe/config/BBC.java index 48e4bce7..afe1eb5d 100644 --- a/core/src/main/java/com/boydti/fawe/config/BBC.java +++ b/core/src/main/java/com/boydti/fawe/config/BBC.java @@ -51,6 +51,10 @@ public enum BBC { WORLDEDIT_CANCEL_REASON_MAX_FAILS("Outside allowed region", "Cancel"), WORLDEDIT_FAILED_LOAD_CHUNK("&cSkipped loading chunk: &7%s0;%s1&c. Try increasing chunk-wait.", "Cancel"), + LOADING_CLIPBOARD("&dLoading clipboard from disk, please wait.", "History"), + INDEXING_HISTORY("&dIndexing %s history objects on disk, please wait.", "History"), + INDEXING_COMPLETE("&dIndexing complete. Took: %s seconds!", "History"), + WORLDEDIT_OOM_ADMIN("&cPossible options:\n&8 - &7//fast\n&8 - &7Do smaller edits\n&8 - &7Allocate more memory\n&8 - &7Disable this safeguard", "Info"), NOT_PLAYER("&cYou must be a player to perform this action!", "Error"), COMPRESSED("History compressed. Saved ~ %s0b (%s1x smaller)", "Info"), diff --git a/core/src/main/java/com/boydti/fawe/config/Settings.java b/core/src/main/java/com/boydti/fawe/config/Settings.java index 4d5d88cc..0dca8739 100644 --- a/core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/core/src/main/java/com/boydti/fawe/config/Settings.java @@ -24,6 +24,7 @@ public class Settings { public static boolean STORE_CLIPBOARD_ON_DISK = false; public static int DELETE_HISTORY_AFTER_DAYS = 7; + public static int DELETE_CLIPBOARD_AFTER_DAYS = 0; public static int COMPRESSION_LEVEL = 0; public static int BUFFER_SIZE = 531441; public static boolean METRICS = true; @@ -86,6 +87,7 @@ public class Settings { options.put("lighting.fix-all", FIX_ALL_LIGHTING); options.put("lighting.async", ASYNC_LIGHTING); options.put("clipboard.use-disk", STORE_CLIPBOARD_ON_DISK); + options.put("clipboard.delete-after-days", DELETE_CLIPBOARD_AFTER_DAYS); options.put("history.use-disk", STORE_HISTORY_ON_DISK); options.put("history.compress", false); options.put("history.chunk-wait-ms", CHUNK_WAIT); @@ -136,7 +138,7 @@ public class Settings { ALLOWED_3RDPARTY_EXTENTS = config.getStringList("extent.allowed-plugins"); EXTENT_DEBUG = config.getBoolean("extent.debug"); STORE_CLIPBOARD_ON_DISK = config.getBoolean("clipboard.use-disk"); - + DELETE_CLIPBOARD_AFTER_DAYS = config.getInt("clipboard.delete-after-days"); if (STORE_HISTORY_ON_DISK = config.getBoolean("history.use-disk")) { LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE; } diff --git a/core/src/main/java/com/boydti/fawe/object/BufferedRandomAccessFile.java b/core/src/main/java/com/boydti/fawe/object/BufferedRandomAccessFile.java new file mode 100644 index 00000000..97414c5a --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/BufferedRandomAccessFile.java @@ -0,0 +1,373 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.boydti.fawe.object; + +import java.io.*; +import java.util.Arrays; + + +/** + * A BufferedRandomAccessFile is like a + * RandomAccessFile, but it uses a private buffer so that most + * operations do not require a disk access. + *

+ * + * Note: The operations on this class are unmonitored. Also, the correct + * functioning of the RandomAccessFile methods that are not + * overridden here relies on the implementation of those methods in the + * superclass. + * Author : Avinash Lakshman ( alakshman@facebook.com) & Prashant Malik ( pmalik@facebook.com ) + */ + +public final class BufferedRandomAccessFile extends RandomAccessFile +{ + static final int LogBuffSz_ = 16; // 64K buffer + public static final int BuffSz_ = (1 << LogBuffSz_); + static final long BuffMask_ = ~(((long) BuffSz_) - 1L); + + /* + * This implementation is based on the buffer implementation in Modula-3's + * "Rd", "Wr", "RdClass", and "WrClass" interfaces. + */ + private boolean dirty_; // true iff unflushed bytes exist + private boolean closed_; // true iff the file is closed + private long curr_; // current position in file + private long lo_, hi_; // bounds on characters in "buff" + private byte[] buff_; // local buffer + private long maxHi_; // this.lo + this.buff.length + private boolean hitEOF_; // buffer contains last file block? + private long diskPos_; // disk position + + /* + * To describe the above fields, we introduce the following abstractions for + * the file "f": + * + * len(f) the length of the file curr(f) the current position in the file + * c(f) the abstract contents of the file disk(f) the contents of f's + * backing disk file closed(f) true iff the file is closed + * + * "curr(f)" is an index in the closed interval [0, len(f)]. "c(f)" is a + * character sequence of length "len(f)". "c(f)" and "disk(f)" may differ if + * "c(f)" contains unflushed writes not reflected in "disk(f)". The flush + * operation has the effect of making "disk(f)" identical to "c(f)". + * + * A file is said to be *valid* if the following conditions hold: + * + * V1. The "closed" and "curr" fields are correct: + * + * f.closed == closed(f) f.curr == curr(f) + * + * V2. The current position is either contained in the buffer, or just past + * the buffer: + * + * f.lo <= f.curr <= f.hi + * + * V3. Any (possibly) unflushed characters are stored in "f.buff": + * + * (forall i in [f.lo, f.curr): c(f)[i] == f.buff[i - f.lo]) + * + * V4. For all characters not covered by V3, c(f) and disk(f) agree: + * + * (forall i in [f.lo, len(f)): i not in [f.lo, f.curr) => c(f)[i] == + * disk(f)[i]) + * + * V5. "f.dirty" is true iff the buffer contains bytes that should be + * flushed to the file; by V3 and V4, only part of the buffer can be dirty. + * + * f.dirty == (exists i in [f.lo, f.curr): c(f)[i] != f.buff[i - f.lo]) + * + * V6. this.maxHi == this.lo + this.buff.length + * + * Note that "f.buff" can be "null" in a valid file, since the range of + * characters in V3 is empty when "f.lo == f.curr". + * + * A file is said to be *ready* if the buffer contains the current position, + * i.e., when: + * + * R1. !f.closed && f.buff != null && f.lo <= f.curr && f.curr < f.hi + * + * When a file is ready, reading or writing a single byte can be performed + * by reading or writing the in-memory buffer without performing a disk + * operation. + */ + + /** + * Open a new BufferedRandomAccessFile on file + * in mode mode, which should be "r" for reading only, or + * "rw" for reading and writing. + */ + public BufferedRandomAccessFile(File file, String mode) throws IOException + { + super(file, mode); + this.init(0); + } + + public BufferedRandomAccessFile(File file, String mode, int size) throws IOException + { + super(file, mode); + this.init(size); + } + + /** + * Open a new BufferedRandomAccessFile on the file named + * name in mode mode, which should be "r" for + * reading only, or "rw" for reading and writing. + */ + public BufferedRandomAccessFile(String name, String mode) throws IOException + { + super(name, mode); + this.init(0); + } + + public BufferedRandomAccessFile(String name, String mode, int size) throws FileNotFoundException + { + super(name, mode); + this.init(size); + } + + private void init(int size) + { + this.dirty_ = this.closed_ = false; + this.lo_ = this.curr_ = this.hi_ = 0; + this.buff_ = (size > BuffSz_) ? new byte[size] : new byte[BuffSz_]; + this.maxHi_ = (long) BuffSz_; + this.hitEOF_ = false; + this.diskPos_ = 0L; + } + + public void close() throws IOException + { + this.flush(); + this.closed_ = true; + super.close(); + } + + /** + * Flush any bytes in the file's buffer that have not yet been written to + * disk. If the file was created read-only, this method is a no-op. + */ + public void flush() throws IOException + { + this.flushBuffer(); + } + + /* Flush any dirty bytes in the buffer to disk. */ + private void flushBuffer() throws IOException + { + if (this.dirty_) + { + if (this.diskPos_ != this.lo_) + super.seek(this.lo_); + int len = (int) (this.curr_ - this.lo_); + super.write(this.buff_, 0, len); + this.diskPos_ = this.curr_; + this.dirty_ = false; + } + } + + /* + * Read at most "this.buff.length" bytes into "this.buff", returning the + * number of bytes read. If the return result is less than + * "this.buff.length", then EOF was read. + */ + private int fillBuffer() throws IOException + { + int cnt = 0; + int rem = this.buff_.length; + while (rem > 0) + { + int n = super.read(this.buff_, cnt, rem); + if (n < 0) + break; + cnt += n; + rem -= n; + } + if ( (cnt < 0) && (this.hitEOF_ = (cnt < this.buff_.length)) ) + { + // make sure buffer that wasn't read is initialized with -1 + Arrays.fill(this.buff_, cnt, this.buff_.length, (byte) 0xff); + } + this.diskPos_ += cnt; + return cnt; + } + + /* + * This method positions this.curr at position pos. + * If pos does not fall in the current buffer, it flushes the + * current buffer and loads the correct one.

+ * + * On exit from this routine this.curr == this.hi iff pos + * is at or past the end-of-file, which can only happen if the file was + * opened in read-only mode. + */ + public void seek(long pos) throws IOException + { + if (pos >= this.hi_ || pos < this.lo_) + { + // seeking outside of current buffer -- flush and read + this.flushBuffer(); + this.lo_ = pos & BuffMask_; // start at BuffSz boundary + this.maxHi_ = this.lo_ + (long) this.buff_.length; + if (this.diskPos_ != this.lo_) + { + super.seek(this.lo_); + this.diskPos_ = this.lo_; + } + int n = this.fillBuffer(); + this.hi_ = this.lo_ + (long) n; + } + else + { + // seeking inside current buffer -- no read required + if (pos < this.curr_) + { + // if seeking backwards, we must flush to maintain V4 + this.flushBuffer(); + } + } + this.curr_ = pos; + } + + public long getFilePointer() + { + return this.curr_; + } + + public long length() throws IOException + { + return Math.max(this.curr_, super.length()); + } + + public int read() throws IOException + { + if (this.curr_ >= this.hi_) + { + // test for EOF + // if (this.hi < this.maxHi) return -1; + if (this.hitEOF_) + return -1; + + // slow path -- read another buffer + this.seek(this.curr_); + if (this.curr_ == this.hi_) + return -1; + } + byte res = this.buff_[(int) (this.curr_ - this.lo_)]; + this.curr_++; + return ((int) res) & 0xFF; // convert byte -> int + } + + public int read(byte[] b) throws IOException + { + return this.read(b, 0, b.length); + } + + public int read(byte[] b, int off, int len) throws IOException + { + if (this.curr_ >= this.hi_) + { + // test for EOF + // if (this.hi < this.maxHi) return -1; + if (this.hitEOF_) + return -1; + + // slow path -- read another buffer + this.seek(this.curr_); + if (this.curr_ == this.hi_) + return -1; + } + len = Math.min(len, (int) (this.hi_ - this.curr_)); + int buffOff = (int) (this.curr_ - this.lo_); + System.arraycopy(this.buff_, buffOff, b, off, len); + this.curr_ += len; + return len; + } + + public void write(int b) throws IOException + { + if (this.curr_ >= this.hi_) + { + if (this.hitEOF_ && this.hi_ < this.maxHi_) + { + // at EOF -- bump "hi" + this.hi_++; + } + else + { + // slow path -- write current buffer; read next one + this.seek(this.curr_); + if (this.curr_ == this.hi_) + { + // appending to EOF -- bump "hi" + this.hi_++; + } + } + } + this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b; + this.curr_++; + this.dirty_ = true; + } + + public void write(byte[] b) throws IOException + { + this.write(b, 0, b.length); + } + + public void write(byte[] b, int off, int len) throws IOException + { + while (len > 0) + { + int n = this.writeAtMost(b, off, len); + off += n; + len -= n; + this.dirty_ = true; + } + } + + /* + * Write at most "len" bytes to "b" starting at position "off", and return + * the number of bytes written. + */ + private int writeAtMost(byte[] b, int off, int len) throws IOException + { + if (this.curr_ >= this.hi_) + { + if (this.hitEOF_ && this.hi_ < this.maxHi_) + { + // at EOF -- bump "hi" + this.hi_ = this.maxHi_; + } + else + { + // slow path -- write current buffer; read next one + this.seek(this.curr_); + if (this.curr_ == this.hi_) + { + // appending to EOF -- bump "hi" + this.hi_ = this.maxHi_; + } + } + } + len = Math.min(len, (int) (this.hi_ - this.curr_)); + int buffOff = (int) (this.curr_ - this.lo_); + System.arraycopy(b, off, this.buff_, buffOff, len); + this.curr_ += len; + return len; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java index fa678dea..c7d5b31b 100644 --- a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java +++ b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java @@ -5,6 +5,7 @@ import com.boydti.fawe.FaweAPI; import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.changeset.DiskStorageHistory; +import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard; import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.WEManager; import com.boydti.fawe.wrappers.PlayerWrapper; @@ -13,10 +14,13 @@ import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionSelector; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; +import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.registry.WorldData; import java.io.File; import java.lang.reflect.Field; import java.util.ArrayList; @@ -95,12 +99,33 @@ public abstract class FawePlayer { loadSessionsFromDisk(world); } } + loadClipboardFromDisk(); } catch (Exception e) { e.printStackTrace(); Fawe.debug("Failed to load history for: " + getName()); } } + public void loadClipboardFromDisk() { + try { + File file = new File(Fawe.imp().getDirectory(), "clipboard" + File.separator + getUUID()); + if (file.exists()) { + DiskOptimizedClipboard doc = new DiskOptimizedClipboard(file); + Player player = getPlayer(); + LocalSession session = getSession(); + if (player != null && session != null) { + sendMessage("&d" + BBC.PREFIX.s() + " " + BBC.LOADING_CLIPBOARD.s()); + WorldData worldData = player.getWorld().getWorldData(); + Clipboard clip = doc.toClipboard(); + ClipboardHolder holder = new ClipboardHolder(clip, worldData); + getSession().setClipboard(holder); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + /** * Get the current World * @return @@ -118,35 +143,38 @@ public abstract class FawePlayer { if (world == null) { return; } - TaskManager.IMP.async(new Runnable() { - @Override - public void run() { - UUID uuid = getUUID(); - List editIds = new ArrayList<>(); - File folder = new File(Fawe.imp().getDirectory(), "history" + File.separator + world.getName() + File.separator + uuid); - if (folder.isDirectory()) { - for (File file : folder.listFiles()) { - if (file.getName().endsWith(".bd")) { - int index = Integer.parseInt(file.getName().split("\\.")[0]); - editIds.add(index); - } - } + final long start = System.currentTimeMillis(); + final UUID uuid = getUUID(); + final List editIds = new ArrayList<>(); + final File folder = new File(Fawe.imp().getDirectory(), "history" + File.separator + world.getName() + File.separator + uuid); + if (folder.isDirectory()) { + for (File file : folder.listFiles()) { + if (file.getName().endsWith(".bd")) { + int index = Integer.parseInt(file.getName().split("\\.")[0]); + editIds.add(index); } - Collections.sort(editIds); - if (editIds.size() > 0) { - Fawe.debug(BBC.PREFIX.s() + " Indexing " + editIds.size() + " history objects for " + getName()); - for (int index : editIds) { + } + } + if (editIds.size() > 0) { + sendMessage("&d" + BBC.PREFIX.s() + " " + BBC.INDEXING_HISTORY.format(editIds.size())); + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + Collections.sort(editIds); + for (int i = editIds.size() - 1; i >= 0; i--) { + int index = editIds.get(i); DiskStorageHistory set = new DiskStorageHistory(world, uuid, index); EditSession edit = set.toEditSession(getPlayer()); if (world.equals(getWorld())) { - session.remember(edit); + session.remember(edit, 0); } else { return; } } + sendMessage("&d" + BBC.PREFIX.s() + " " + BBC.INDEXING_COMPLETE.format((System.currentTimeMillis() - start) / 1000d)); } - } - }); + }); + } } /** diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java index 27e93c0a..0d262a4b 100644 --- a/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java @@ -2,14 +2,19 @@ package com.boydti.fawe.object.clipboard; import com.boydti.fawe.Fawe; import com.boydti.fawe.FaweCache; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.BufferedRandomAccessFile; import com.boydti.fawe.object.IntegerTrio; import com.boydti.fawe.util.TaskManager; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.regions.CuboidRegion; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; @@ -28,55 +33,167 @@ import java.util.UUID; */ public class DiskOptimizedClipboard extends FaweClipboard { + private static int HEADER_SIZE = 10; + + protected int length; + protected int height; + protected int width; + protected int area; + private final HashMap nbtMap; private final HashSet entities; private final File file; private final byte[] buffer; - private volatile RandomAccessFile raf; + private volatile BufferedRandomAccessFile raf; private long lastAccessed; private int last; + public DiskOptimizedClipboard(int width, int height, int length, UUID uuid) { + this(width, height, length, new File(Fawe.imp().getDirectory(), "clipboard" + File.separator + uuid)); + } + + public DiskOptimizedClipboard(File file) { + nbtMap = new HashMap<>(); + entities = new HashSet<>();this.buffer = new byte[2]; + this.file = file; + this.lastAccessed = System.currentTimeMillis(); + try { + this.raf = new BufferedRandomAccessFile(file, "rw", Settings.BUFFER_SIZE); + raf.setLength(file.length()); + long size = (raf.length() - HEADER_SIZE) / 2; + raf.seek(0); + last = -1; + raf.read(buffer); + width = (((buffer[1] & 0xFF) << 8) + ((buffer[0] & 0xFF))); + raf.read(buffer); + length = (((buffer[1] & 0xFF) << 8) + ((buffer[0] & 0xFF))); + height = (int) (size / (width * length)); + area = width * length; + } catch (IOException e) { + e.printStackTrace(); + } + autoCloseTask(); + } + + public BlockArrayClipboard toClipboard() { + try { + CuboidRegion region = new CuboidRegion(new Vector(0, 0, 0), new Vector(width - 1, height - 1, length - 1)) { + @Override + public boolean contains(Vector position) { + return true; + } + }; + if (raf == null) { + open(); + } + raf.seek(4); + last = -1; + int ox = (((byte) raf.read() << 8) | ((byte) raf.read()) & 0xFF); + int oy = (((byte) raf.read() << 8) | ((byte) raf.read()) & 0xFF); + int oz = (((byte) raf.read() << 8) | ((byte) raf.read()) & 0xFF); + BlockArrayClipboard clipboard = new BlockArrayClipboard(region, this); + clipboard.setOrigin(new Vector(ox, oy, oz)); + return clipboard; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public void setOrigin(Vector offset) { + try { + if (raf == null) { + open(); + } + raf.seek(4); + last = -1; + raf.write((byte) (offset.getBlockX() >> 8)); + raf.write((byte) (offset.getBlockX())); + + raf.write((byte) (offset.getBlockY() >> 8)); + raf.write((byte) (offset.getBlockY())); + + raf.write((byte) (offset.getBlockZ() >> 8)); + raf.write((byte) (offset.getBlockZ())); + } catch (IOException e) { + e.printStackTrace(); + } + } + public DiskOptimizedClipboard(int width, int height, int length, File file) { - super(width, height, length); nbtMap = new HashMap<>(); entities = new HashSet<>(); this.file = file; this.buffer = new byte[2]; this.lastAccessed = System.currentTimeMillis(); + this.width = width; + this.height = height; + this.length = length; + this.area = width * length; try { if (!file.exists()) { file.getParentFile().mkdirs(); - file.createNewFile(); } + file.createNewFile(); } catch (Exception e) { e.printStackTrace(); } } + public void flush() { + try { + raf.close(); + raf = null; + file.setWritable(true); + System.gc(); + } catch (IOException e) { + e.printStackTrace(); + } + } + public DiskOptimizedClipboard(int width, int height, int length) { this(width, height, length, new File(Fawe.imp().getDirectory(), "clipboard" + File.separator + UUID.randomUUID())); } - + public void close() { + try { + RandomAccessFile tmp = raf; + raf = null; + tmp.close(); + tmp = null; + System.gc(); + } catch (IOException e) { + e.printStackTrace(); + } + } public void open() throws IOException { - this.raf = new RandomAccessFile(file, "rw"); + if (raf != null) { + close(); + } + this.raf = new BufferedRandomAccessFile(file, "rw", Settings.BUFFER_SIZE); long size = width * height * length * 2l; if (raf.length() != size) { - raf.setLength(size); + raf.setLength(size + HEADER_SIZE); + // write length etc + raf.seek(0); + last = 0; + raf.write((width) & 0xff); + raf.write(((width) >> 8) & 0xff); + raf.write((length) & 0xff); + raf.write(((length) >> 8) & 0xff); } + autoCloseTask(); + } + + private void autoCloseTask() { TaskManager.IMP.laterAsync(new Runnable() { @Override public void run() { if (raf != null && System.currentTimeMillis() - lastAccessed > 10000) { - try { - RandomAccessFile tmp = raf; - raf = null; - tmp.close(); - } catch (IOException e) { - e.printStackTrace(); - } + close(); } else if (raf == null) { return; } else { @@ -95,7 +212,7 @@ public class DiskOptimizedClipboard extends FaweClipboard { lastAccessed = System.currentTimeMillis(); int i = x + z * width + y * area; if (i != last + 1) { - raf.seek(i << 1); + raf.seek((HEADER_SIZE) + (i << 1)); } raf.read(buffer); last = i; @@ -129,8 +246,9 @@ public class DiskOptimizedClipboard extends FaweClipboard { lastAccessed = System.currentTimeMillis(); int i = x + z * width + y * area; if (i != last + 1) { - raf.seek(i << 1); + raf.seek((HEADER_SIZE) + (i << 1)); } + last = i; final int id = block.getId(); final int data = block.getData(); int combined = (id << 4) + data; diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java index 0a84fe00..959889e5 100644 --- a/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java @@ -1,5 +1,6 @@ package com.boydti.fawe.object.clipboard; +import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; @@ -12,18 +13,6 @@ import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; public abstract class FaweClipboard { - public final int length; - public final int height; - public final int width; - public final int area; - - public FaweClipboard(int width, int height, int length) { - this.width = width; - this.height = height; - this.length = length; - this.area = width * length; - } - public abstract BaseBlock getBlock(int x, int y, int z); public abstract boolean setBlock(int x, int y, int z, BaseBlock block); @@ -34,6 +23,8 @@ public abstract class FaweClipboard { public abstract boolean remove(ClipboardEntity clipboardEntity); + public void setOrigin(Vector offset) {} // Do nothing + /** * Stores entity data. */ diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java index 9e8cddb5..6d4d5f62 100644 --- a/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java @@ -13,6 +13,10 @@ import java.util.HashSet; import java.util.List; public class MemoryOptimizedClipboard extends FaweClipboard { + protected int length; + protected int height; + protected int width; + protected int area; // x,z,y+15>>4 | y&15 private final byte[][] ids; @@ -21,10 +25,13 @@ public class MemoryOptimizedClipboard extends FaweClipboard { private final HashSet entities; public MemoryOptimizedClipboard(int width, int height, int length) { - super(width, height, length); + this.width = width; + this.height = height; + this.length = length; + this.area = width * length; ids = new byte[width * length * ((height + 15) >> 4)][]; nbtMap = new HashMap<>(); - entities = new HashSet(); + entities = new HashSet<>(); } @Override diff --git a/core/src/main/java/com/boydti/fawe/util/MainUtil.java b/core/src/main/java/com/boydti/fawe/util/MainUtil.java index e3e2a36a..2a047c1c 100644 --- a/core/src/main/java/com/boydti/fawe/util/MainUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/MainUtil.java @@ -35,6 +35,24 @@ public class MainUtil { Fawe.debug(s); } + public static void warnDeprecated(Class... alternatives) { + StackTraceElement[] stack = new RuntimeException().getStackTrace(); + if (stack.length > 1) { + try { + StackTraceElement creatorElement = stack[stack.length - 2]; + String className = creatorElement.getClassName(); + Class clazz = Class.forName(className); + String creator = clazz.getSimpleName(); + String packageName = clazz.getPackage().getName(); + + StackTraceElement deprecatedElement = stack[stack.length - 1]; + String myName = Class.forName(deprecatedElement.getFileName()).getSimpleName(); + Fawe.debug("@" + creator + " from " + packageName +": " + myName + " is deprecated."); + Fawe.debug(" - Alternatives: " + StringMan.getString(alternatives)); + } catch (Throwable ignore) {} + } + } + public static void iterateFiles(File directory, RunnableVal task) { if (directory.exists()) { File[] files = directory.listFiles(); diff --git a/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java b/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java index d60d8a2a..d0713371 100644 --- a/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java +++ b/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java @@ -21,12 +21,14 @@ package com.sk89q.worldedit; import com.boydti.fawe.FaweCache; import com.boydti.fawe.object.IntegerTrio; +import com.boydti.fawe.util.MainUtil; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BlockID; import com.sk89q.worldedit.command.ClipboardCommands; import com.sk89q.worldedit.command.SchematicCommands; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.regions.CuboidRegion; @@ -76,7 +78,7 @@ public class CuboidClipboard { public byte[][] ids; public byte[][] datas; public HashMap nbtMap; - public List entities = new ArrayList(); + public List entities = new ArrayList<>(); public Vector size; private int dx; @@ -91,6 +93,7 @@ public class CuboidClipboard { */ public CuboidClipboard(Vector size) { checkNotNull(size); + MainUtil.warnDeprecated(BlockArrayClipboard.class); origin = new Vector(); offset = new Vector(); this.size = size; diff --git a/core/src/main/java/com/sk89q/worldedit/LocalSession.java b/core/src/main/java/com/sk89q/worldedit/LocalSession.java index 3904f908..92e19b92 100644 --- a/core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -20,6 +20,8 @@ package com.sk89q.worldedit; import com.boydti.fawe.object.changeset.FaweChangeSet; +import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard; +import com.boydti.fawe.util.FaweQueue; import com.boydti.fawe.util.SetQueue; import com.sk89q.jchronic.Chronic; import com.sk89q.jchronic.Options; @@ -32,6 +34,8 @@ import com.sk89q.worldedit.command.tool.SinglePickaxe; import com.sk89q.worldedit.command.tool.Tool; import com.sk89q.worldedit.entity.Player; 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.extent.inventory.BlockBag; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Masks; @@ -190,10 +194,16 @@ public class LocalSession { * @param editSession the edit session */ public void remember(EditSession editSession) { - checkNotNull(editSession); + remember(editSession, history.size()); + } + public void remember(EditSession editSession, int index) { + // Enqueue it if (editSession.getQueue() != null) { - SetQueue.IMP.enqueue(editSession.getQueue()); + FaweQueue queue = editSession.getQueue(); + if (queue.size() > 0) { + SetQueue.IMP.enqueue(editSession.getQueue()); + } } // Don't store anything if no changes were made @@ -207,7 +217,7 @@ public class LocalSession { if (set instanceof FaweChangeSet) { ((FaweChangeSet) set).flush(); } - history.add(editSession); + history.add(Math.max(0, Math.min(index, history.size())), editSession); while (history.size() > MAX_HISTORY_SIZE) { history.remove(0); } @@ -456,6 +466,15 @@ public class LocalSession { * @param clipboard the clipboard, or null if the clipboard is to be cleared */ public void setClipboard(@Nullable ClipboardHolder clipboard) { + if (this.clipboard != null && clipboard != null) { + Clipboard clip = clipboard.getClipboard(); + if (clip instanceof BlockArrayClipboard) { + BlockArrayClipboard bac = (BlockArrayClipboard) clip; + if (bac.IMP instanceof DiskOptimizedClipboard) { + ((DiskOptimizedClipboard) bac.IMP).flush(); + } + } + } this.clipboard = clipboard; } diff --git a/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java new file mode 100644 index 00000000..943546e3 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java @@ -0,0 +1,263 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.command; + +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.minecraft.util.commands.Logging; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Player; +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.function.block.BlockReplace; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.internal.annotation.Direction; +import com.sk89q.worldedit.internal.annotation.Selection; +import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.regions.RegionSelector; +import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.util.command.binding.Switch; +import com.sk89q.worldedit.util.command.parametric.Optional; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.minecraft.util.commands.Logging.LogMode.PLACEMENT; +import static com.sk89q.minecraft.util.commands.Logging.LogMode.REGION; + +/** + * Clipboard commands. + */ +public class ClipboardCommands { + + private final WorldEdit worldEdit; + + /** + * Create a new instance. + * + * @param worldEdit reference to WorldEdit + */ + public ClipboardCommands(WorldEdit worldEdit) { + checkNotNull(worldEdit); + this.worldEdit = worldEdit; + } + + @Command( + aliases = { "/copy" }, + flags = "em", + desc = "Copy the selection to the clipboard", + help = "Copy the selection to the clipboard\n" + + "Flags:\n" + + " -e controls whether entities are copied\n" + + " -m sets a source mask so that excluded blocks become air\n" + + "WARNING: Pasting entities cannot yet be undone!", + min = 0, + max = 0 + ) + @CommandPermissions("worldedit.clipboard.copy") + public void copy(Player player, LocalSession session, EditSession editSession, + @Selection Region region, @Switch('e') boolean copyEntities, + @Switch('m') Mask mask) throws WorldEditException { + + BlockArrayClipboard clipboard = new BlockArrayClipboard(region, player.getUniqueId()); + + + clipboard.setOrigin(session.getPlacementPosition(player)); + ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint()); + if (mask != null) { + copy.setSourceMask(mask); + } + Operations.completeLegacy(copy); + session.setClipboard(new ClipboardHolder(clipboard, editSession.getWorld().getWorldData())); + + player.print(region.getArea() + " block(s) were copied."); + } + + @Command( + aliases = { "/cut" }, + flags = "em", + usage = "[leave-id]", + desc = "Cut the selection to the clipboard", + help = "Copy the selection to the clipboard\n" + + "Flags:\n" + + " -e controls whether entities are copied\n" + + " -m sets a source mask so that excluded blocks become air\n" + + "WARNING: Cutting and pasting entities cannot yet be undone!", + min = 0, + max = 1 + ) + @CommandPermissions("worldedit.clipboard.cut") + @Logging(REGION) + public void cut(Player player, LocalSession session, EditSession editSession, + @Selection Region region, @Optional("air") Pattern leavePattern, @Switch('e') boolean copyEntities, + @Switch('m') Mask mask) throws WorldEditException { + + BlockArrayClipboard clipboard = new BlockArrayClipboard(region, player.getUniqueId()); + clipboard.setOrigin(session.getPlacementPosition(player)); + ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint()); + copy.setSourceFunction(new BlockReplace(editSession, leavePattern)); + if (mask != null) { + copy.setSourceMask(mask); + } + Operations.completeLegacy(copy); + session.setClipboard(new ClipboardHolder(clipboard, editSession.getWorld().getWorldData())); + + player.print(region.getArea() + " block(s) were copied."); + } + + @Command( + aliases = { "/paste" }, + usage = "", + flags = "sao", + desc = "Paste the clipboard's contents", + help = + "Pastes the clipboard's contents.\n" + + "Flags:\n" + + " -a skips air blocks\n" + + " -o pastes at the original position\n" + + " -s selects the region after pasting", + min = 0, + max = 0 + ) + @CommandPermissions("worldedit.clipboard.paste") + @Logging(PLACEMENT) + public void paste(Player player, LocalSession session, EditSession editSession, + @Switch('a') boolean ignoreAirBlocks, @Switch('o') boolean atOrigin, + @Switch('s') boolean selectPasted) throws WorldEditException { + + ClipboardHolder holder = session.getClipboard(); + Clipboard clipboard = holder.getClipboard(); + Region region = clipboard.getRegion(); + + Vector to = atOrigin ? clipboard.getOrigin() : session.getPlacementPosition(player); + Operation operation = holder + .createPaste(editSession, editSession.getWorld().getWorldData()) + .to(to) + .ignoreAirBlocks(ignoreAirBlocks) + .build(); + Operations.completeLegacy(operation); + + if (selectPasted) { + Vector max = to.add(region.getMaximumPoint().subtract(region.getMinimumPoint())); + RegionSelector selector = new CuboidRegionSelector(player.getWorld(), to, max); + session.setRegionSelector(player.getWorld(), selector); + selector.learnChanges(); + selector.explainRegionAdjust(player, session); + } + + player.print("The clipboard has been pasted at " + to); + } + + @Command( + aliases = { "/rotate" }, + usage = " [] []", + desc = "Rotate the contents of the clipboard", + help = "Non-destructively rotate the contents of the clipboard.\n" + + "Angles are provided in degrees and a positive angle will result in a clockwise rotation. " + + "Multiple rotations can be stacked. Interpolation is not performed so angles should be a multiple of 90 degrees.\n" + ) + @CommandPermissions("worldedit.clipboard.rotate") + public void rotate(Player player, LocalSession session, Double yRotate, @Optional Double xRotate, @Optional Double zRotate) throws WorldEditException { + if ((yRotate != null && Math.abs(yRotate % 90) > 0.001) || + xRotate != null && Math.abs(xRotate % 90) > 0.001 || + zRotate != null && Math.abs(zRotate % 90) > 0.001) { + player.printDebug("Note: Interpolation is not yet supported, so angles that are multiples of 90 is recommended."); + } + + ClipboardHolder holder = session.getClipboard(); + AffineTransform transform = new AffineTransform(); + transform = transform.rotateY(-(yRotate != null ? yRotate : 0)); + transform = transform.rotateX(-(xRotate != null ? xRotate : 0)); + transform = transform.rotateZ(-(zRotate != null ? zRotate : 0)); + holder.setTransform(holder.getTransform().combine(transform)); + player.print("The clipboard copy has been rotated."); + } + + @Command( + aliases = { "/flip" }, + usage = "[]", + desc = "Flip the contents of the clipboard", + help = + "Flips the contents of the clipboard across the point from which the copy was made.\n", + min = 0, + max = 1 + ) + @CommandPermissions("worldedit.clipboard.flip") + public void flip(Player player, LocalSession session, EditSession editSession, + @Optional(Direction.AIM) @Direction Vector direction) throws WorldEditException { + ClipboardHolder holder = session.getClipboard(); + Clipboard clipboard = holder.getClipboard(); + AffineTransform transform = new AffineTransform(); + transform = transform.scale(direction.positive().multiply(-2).add(1, 1, 1)); + holder.setTransform(holder.getTransform().combine(transform)); + player.print("The clipboard copy has been flipped."); + } + + @Command( + aliases = { "/load" }, + usage = "", + desc = "Load a schematic into your clipboard", + min = 0, + max = 1 + ) + @Deprecated + @CommandPermissions("worldedit.clipboard.load") + public void load(Actor actor) { + actor.printError("This command is no longer used. See //schematic load."); + } + + @Command( + aliases = { "/save" }, + usage = "", + desc = "Save a schematic into your clipboard", + min = 0, + max = 1 + ) + @Deprecated + @CommandPermissions("worldedit.clipboard.save") + public void save(Actor actor) { + actor.printError("This command is no longer used. See //schematic save."); + } + + @Command( + aliases = { "clearclipboard" }, + usage = "", + desc = "Clear your clipboard", + min = 0, + max = 0 + ) + @CommandPermissions("worldedit.clipboard.clear") + public void clearClipboard(Player player, LocalSession session, EditSession editSession) throws WorldEditException { + session.setClipboard(null); + player.print("Clipboard cleared."); + } + public static Class inject() { + return ClipboardCommands.class; + } +} diff --git a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index 96f206d7..0ddde6e1 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -37,6 +37,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; +import com.sk89q.worldedit.extent.clipboard.io.SchematicReader; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.session.ClipboardHolder; @@ -117,9 +118,13 @@ public class SchematicCommands { final ClipboardReader reader = format.getReader(bis); final WorldData worldData = player.getWorld().getWorldData(); - final Clipboard clipboard = reader.read(player.getWorld().getWorldData()); + final Clipboard clipboard; + if (reader instanceof SchematicReader) { + clipboard = ((SchematicReader) reader).read(player.getWorld().getWorldData(), player.getUniqueId()); + } else { + clipboard = reader.read(player.getWorld().getWorldData()); + } session.setClipboard(new ClipboardHolder(clipboard, worldData)); - log.info(player.getName() + " loaded " + filePath); player.print(filename + " loaded. Paste it with //paste"); } @@ -160,7 +165,7 @@ public class SchematicCommands { // If we have a transform, bake it into the copy if (!transform.isIdentity()) { final FlattenedClipboardTransform result = FlattenedClipboardTransform.transform(clipboard, transform, holder.getWorldData()); - target = new BlockArrayClipboard(result.getTransformedRegion()); + target = new BlockArrayClipboard(result.getTransformedRegion(), player.getUniqueId()); target.setOrigin(clipboard.getOrigin()); Operations.completeLegacy(result.copyTo(target)); } else { diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java index 147fad17..84f8a1f8 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java @@ -37,6 +37,7 @@ import com.sk89q.worldedit.world.biome.BaseBiome; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; import javax.annotation.Nullable; @@ -64,6 +65,17 @@ public class BlockArrayClipboard implements Clipboard { private Vector origin; + public BlockArrayClipboard(Region region) { + checkNotNull(region); + this.region = region.clone(); + this.size = getDimensions(); + this.IMP = Settings.STORE_CLIPBOARD_ON_DISK ? new DiskOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ()) : new MemoryOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ()); + this.origin = region.getMinimumPoint(); + this.mx = origin.getBlockX(); + this.my = origin.getBlockY(); + this.mz = origin.getBlockZ(); + } + /** * Create a new instance. * @@ -71,11 +83,22 @@ public class BlockArrayClipboard implements Clipboard { * * @param region the bounding region */ - public BlockArrayClipboard(Region region) { + public BlockArrayClipboard(Region region, UUID clipboardId) { checkNotNull(region); this.region = region.clone(); this.size = getDimensions(); - this.IMP = Settings.STORE_CLIPBOARD_ON_DISK ? new DiskOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ()) : new MemoryOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ()); + this.IMP = Settings.STORE_CLIPBOARD_ON_DISK ? new DiskOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ(), clipboardId) : new MemoryOptimizedClipboard(size.getBlockX(), size.getBlockY(), size.getBlockZ()); + this.origin = region.getMinimumPoint(); + this.mx = origin.getBlockX(); + this.my = origin.getBlockY(); + this.mz = origin.getBlockZ(); + } + + public BlockArrayClipboard(Region region, DiskOptimizedClipboard clipboard) { + checkNotNull(region); + this.region = region.clone(); + this.size = getDimensions(); + this.IMP = clipboard; this.origin = region.getMinimumPoint(); this.mx = origin.getBlockX(); this.my = origin.getBlockY(); @@ -95,6 +118,7 @@ public class BlockArrayClipboard implements Clipboard { @Override public void setOrigin(Vector origin) { this.origin = origin; + IMP.setOrigin(origin.subtract(region.getMinimumPoint())); } @Override diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java index ed4a7014..0a8130d6 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java @@ -44,6 +44,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -71,6 +72,10 @@ public class SchematicReader implements ClipboardReader { @Override public Clipboard read(WorldData data) throws IOException { + return read(data, UUID.randomUUID()); + } + + public Clipboard read(WorldData data, UUID clipboardId) throws IOException { // Schematic tag NamedTag rootTag = inputStream.readNamedTag(); if (!rootTag.getName().equals("Schematic")) { @@ -171,7 +176,7 @@ public class SchematicReader implements ClipboardReader { tileEntitiesMap.put(vec, values); } - BlockArrayClipboard clipboard = new BlockArrayClipboard(region); + BlockArrayClipboard clipboard = new BlockArrayClipboard(region, clipboardId); clipboard.setOrigin(origin); // Don't log a torrent of errors diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..5ccda13e Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..b2c6e7a0 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Feb 22 17:40:44 PST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-bin.zip