Finish clipboard on disk

This commit is contained in:
Jesse Boyd 2016-04-26 04:14:41 +10:00
parent f5bbd59602
commit 57696f25f4
18 changed files with 952 additions and 64 deletions

View File

@ -1,4 +1,23 @@
# Overview
Optimize worldedit
# Main resource page
https://www.spigotmc.org/resources/13932/
# 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

View File

@ -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();

View File

@ -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"),

View File

@ -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;
}

View File

@ -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 <code>BufferedRandomAccessFile</code> is like a
* <code>RandomAccessFile</code>, but it uses a private buffer so that most
* operations do not require a disk access.
* <P>
*
* Note: The operations on this class are unmonitored. Also, the correct
* functioning of the <code>RandomAccessFile</code> 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 <code>BufferedRandomAccessFile</code> on <code>file</code>
* in mode <code>mode</code>, 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 <code>BufferedRandomAccessFile</code> on the file named
* <code>name</code> in mode <code>mode</code>, 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 <code>this.curr</code> at position <code>pos</code>.
* If <code>pos</code> does not fall in the current buffer, it flushes the
* current buffer and loads the correct one.<p>
*
* On exit from this routine <code>this.curr == this.hi</code> iff <code>pos</code>
* 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;
}
}

View File

@ -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<T> {
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<T> {
if (world == null) {
return;
}
TaskManager.IMP.async(new Runnable() {
@Override
public void run() {
UUID uuid = getUUID();
List<Integer> 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<Integer> 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));
}
}
});
});
}
}
/**

View File

@ -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<IntegerTrio, CompoundTag> nbtMap;
private final HashSet<ClipboardEntity> 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;

View File

@ -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.
*/

View File

@ -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<ClipboardEntity> 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<ClipboardEntity>();
entities = new HashSet<>();
}
@Override

View File

@ -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<File> task) {
if (directory.exists()) {
File[] files = directory.listFiles();

View File

@ -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<IntegerTrio, CompoundTag> nbtMap;
public List<CopiedEntity> entities = new ArrayList<CopiedEntity>();
public List<CopiedEntity> 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;

View File

@ -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;
}

View File

@ -0,0 +1,263 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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 = "<y-axis> [<x-axis>] [<z-axis>]",
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 = "[<direction>]",
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 = "<filename>",
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 = "<filename>",
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;
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -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