Optimize copy and load

This commit is contained in:
Jesse Boyd 2017-04-22 21:22:09 +10:00
parent 1f539f4b80
commit 1bd8c3935a
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
14 changed files with 269 additions and 104 deletions

View File

@ -28,7 +28,7 @@ ext {
date = git.head().date.format("yy.MM.dd")
revision = "-${git.head().abbreviatedId}"
parents = git.head().parentIds;
index = -91; // Offset to mach CI
index = -92; // Offset to mach CI
int major, minor, patch;
major = minor = patch = 0;
for (;parents != null && !parents.isEmpty();index++) {

View File

@ -105,6 +105,7 @@ import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.EllipsoidRegion;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.regions.shape.ArbitraryShape;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.session.PasteBuilder;
import com.sk89q.worldedit.session.SessionManager;
import com.sk89q.worldedit.session.request.Request;
@ -460,6 +461,7 @@ public class Fawe {
// Clipboards
BlockArrayClipboard.inject(); // Optimizations + disk
CuboidClipboard.inject(); // Optimizations
ClipboardHolder.inject(); // Closeable
// Regions
CuboidRegion.inject(); // Optimizations
// Extents

View File

@ -39,19 +39,19 @@ public class SchematicStreamer extends NBTStreamer {
addReader("Schematic.Blocks.#", new ByteReader() {
@Override
public void run(int index, int value) {
fc.setId(index, value);
if (value != 0) fc.setId(index, value);
}
});
addReader("Schematic.Data.#", new ByteReader() {
@Override
public void run(int index, int value) {
fc.setData(index, value);
if (value != 0) fc.setData(index, value);
}
});
addReader("Schematic.AddBlocks.#", new ByteReader() {
@Override
public void run(int index, int value) {
fc.setAdd(index, value);
if (value != 0) fc.setAdd(index, value);
}
});

View File

@ -71,6 +71,16 @@ public class AbstractDelegateFaweClipboard extends FaweClipboard {
parent.setDimensions(dimensions);
}
@Override
public void flush() {
parent.flush();
}
@Override
public void close() {
parent.close();
}
@Override
public Vector getDimensions() {
return parent.getDimensions();

View File

@ -5,7 +5,6 @@ import com.boydti.fawe.FaweCache;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.jnbt.NBTStreamer;
import com.boydti.fawe.object.IntegerTrio;
import com.boydti.fawe.object.io.BufferedRandomAccessFile;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.ReflectionUtils;
import com.sk89q.jnbt.CompoundTag;
@ -22,6 +21,11 @@ import com.sk89q.worldedit.regions.CuboidRegion;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -50,8 +54,11 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
private final HashSet<ClipboardEntity> entities;
private final File file;
private final BufferedRandomAccessFile raf;
private RandomAccessFile braf;
private MappedByteBuffer mbb;
private int last;
private FileChannel fc;
public DiskOptimizedClipboard(int width, int height, int length, UUID uuid) {
this(width, height, length, MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.CLIPBOARD + File.separator + uuid + ".bd"));
@ -62,14 +69,16 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
nbtMap = new HashMap<>();
entities = new HashSet<>();
this.file = file;
this.raf = new BufferedRandomAccessFile(file, "rw", 16);
raf.setLength(file.length());
long size = (raf.length() - HEADER_SIZE) >> 1;
raf.seek(2);
this.braf = new RandomAccessFile(file, "rw");
braf.setLength(file.length());
init();
long size = (braf.length() - HEADER_SIZE) >> 1;
mbb.position(2);
last = Integer.MIN_VALUE;
width = (int) raf.readChar();
height = (int) raf.readChar();
length = (int) raf.readChar();
width = (int) mbb.getChar();
height = (int) mbb.getChar();
length = (int) mbb.getChar();
area = width * length;
autoCloseTask();
} catch (IOException e) {
@ -77,6 +86,13 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
}
}
private void init() throws IOException {
if (this.fc == null) {
this.fc = braf.getChannel();
this.mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, file.length());
}
}
@Override
public Vector getDimensions() {
return new Vector(width, height, length);
@ -90,15 +106,15 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
return true;
}
};
raf.seek(8);
mbb.position(8);
last = Integer.MIN_VALUE;
int ox = raf.readShort();
int oy = raf.readShort();
int oz = raf.readShort();
int ox = mbb.getShort();
int oy = mbb.getShort();
int oz = mbb.getShort();
BlockArrayClipboard clipboard = new BlockArrayClipboard(region, this);
clipboard.setOrigin(new Vector(ox, oy, oz));
return clipboard;
} catch (IOException e) {
} catch (Throwable e) {
MainUtil.handleError(e);
}
return null;
@ -115,22 +131,26 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
this.area = width * length;
try {
if (!file.exists()) {
file.getParentFile().mkdirs();
File parent = file.getParentFile();
if (parent != null) {
file.getParentFile().mkdirs();
}
file.createNewFile();
}
} catch (Exception e) {
MainUtil.handleError(e);
}
this.raf = new BufferedRandomAccessFile(file, "rw", 16);
this.braf = new RandomAccessFile(file, "rw");
long volume = (long) width * (long) height * (long) length * 2l + (long) HEADER_SIZE;
raf.setLength(0);
raf.setLength(volume);
braf.setLength(0);
braf.setLength(volume);
init();
// write length etc
raf.seek(2);
mbb.position(2);
last = Integer.MIN_VALUE;
raf.writeChar(width);
raf.writeChar(height);
raf.writeChar(length);
mbb.putChar((char) width);
mbb.putChar((char) height);
mbb.putChar((char) length);
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -139,12 +159,12 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
@Override
public void setOrigin(Vector offset) {
try {
raf.seek(8);
mbb.position(8);
last = Integer.MIN_VALUE;
raf.writeShort(offset.getBlockX());
raf.writeShort(offset.getBlockY());
raf.writeShort(offset.getBlockZ());
} catch (IOException e) {
mbb.putShort((short) offset.getBlockX());
mbb.putShort((short) offset.getBlockY());
mbb.putShort((short) offset.getBlockZ());
} catch (Throwable e) {
MainUtil.handleError(e);
}
}
@ -157,34 +177,51 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
length = dimensions.getBlockZ();
area = width * length;
long size = width * height * length * 2l + HEADER_SIZE;
raf.setLength(size);
raf.seek(2);
braf.setLength(size);
init();
mbb.position(2);
last = Integer.MIN_VALUE;
raf.writeChar(width);
raf.writeChar(height);
raf.writeChar(length);
mbb.putChar((char) width);
mbb.putChar((char) height);
mbb.putChar((char) length);
} catch (IOException e) {
MainUtil.handleError(e);
}
}
@Override
public void flush() {
try {
raf.flush();
} catch (IOException e) {
MainUtil.handleError(e);
}
mbb.force();
}
public DiskOptimizedClipboard(int width, int height, int length) {
this(width, height, length, MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.CLIPBOARD + File.separator + UUID.randomUUID() + ".bd"));
}
private void closeDirectBuffer(ByteBuffer cb) {
if (cb==null || !cb.isDirect()) return;
// we could use this type cast and call functions without reflection code,
// but static import from sun.* package is risky for non-SUN virtual machine.
//try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { }
try {
Method cleaner = cb.getClass().getMethod("cleaner");
cleaner.setAccessible(true);
Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean");
clean.setAccessible(true);
clean.invoke(cleaner.invoke(cb));
} catch(Exception ex) { }
cb = null;
}
@Override
public void close() {
try {
raf.close();
mbb.force();
fc.close();
braf.close();
file.setWritable(true);
System.gc();
closeDirectBuffer(mbb);
} catch (IOException e) {
MainUtil.handleError(e);
}
@ -213,37 +250,37 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
@Override
public void streamIds(NBTStreamer.ByteReader task) {
try {
raf.seek(HEADER_SIZE);
mbb.force();
mbb.position(HEADER_SIZE);
int index = 0;
for (int y = 0; y < height; y++) {
for (int z = 0; z < length; z++) {
for (int x = 0; x < width; x++) {
int combinedId = raf.readChar();
int combinedId = mbb.getChar();
task.run(index++, FaweCache.getId(combinedId));
}
}
}
} catch (IOException e) {
} catch (Throwable e) {
MainUtil.handleError(e);
}
}
public static long start;
@Override
public void streamDatas(NBTStreamer.ByteReader task) {
try {
raf.seek(HEADER_SIZE);
mbb.force();
mbb.position(HEADER_SIZE);
int index = 0;
for (int y = 0; y < height; y++) {
for (int z = 0; z < length; z++) {
for (int x = 0; x < width; x++) {
int combinedId = raf.readChar();
int combinedId = mbb.getChar();
task.run(index++, FaweCache.getData(combinedId));
}
}
}
} catch (IOException e) {
} catch (Throwable e) {
MainUtil.handleError(e);
}
}
@ -256,7 +293,8 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
@Override
public void forEach(final BlockReader task, boolean air) {
try {
raf.seek(HEADER_SIZE);
mbb.force();
mbb.position(HEADER_SIZE);
IntegerTrio trio = new IntegerTrio();
final boolean hasTile = !nbtMap.isEmpty();
if (air) {
@ -264,7 +302,7 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
for (int y = 0; y < height; y++) {
for (int z = 0; z < length; z++) {
for (int x = 0; x < width; x++) {
char combinedId = raf.readChar();
char combinedId = mbb.getChar();
BaseBlock block = FaweCache.CACHE_BLOCK[combinedId];
if (block.canStoreNBTData()) {
trio.set(x, y, z);
@ -282,7 +320,7 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
for (int y = 0; y < height; y++) {
for (int z = 0; z < length; z++) {
for (int x = 0; x < width; x++) {
char combinedId = raf.readChar();
char combinedId = mbb.getChar();
BaseBlock block = FaweCache.CACHE_BLOCK[combinedId];
task.run(x, y, z, block);
}
@ -293,7 +331,7 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
for (int y = 0; y < height; y++) {
for (int z = 0; z < length; z++) {
for (int x = 0; x < width; x++) {
int combinedId = raf.readChar();
int combinedId = mbb.getChar();
if (combinedId != 0) {
BaseBlock block = FaweCache.CACHE_BLOCK[combinedId];
if (block.canStoreNBTData()) {
@ -310,7 +348,7 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
}
}
}
} catch (IOException e) {
} catch (Throwable e) {
MainUtil.handleError(e);
}
}
@ -324,10 +362,10 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
try {
int i = getIndex(x, y, z);
if (i != last + 1) {
raf.seek((HEADER_SIZE) + (i << 1));
mbb.position((HEADER_SIZE) + (i << 1));
}
last = i;
int combinedId = raf.readChar();
int combinedId = mbb.getChar();
BaseBlock block = FaweCache.CACHE_BLOCK[combinedId];
if (block.canStoreNBTData()) {
CompoundTag nbt = nbtMap.get(new IntegerTrio(x, y, z));
@ -358,13 +396,13 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
try {
int i = x + ((ylast == y) ? ylasti : (ylasti = ((ylast = y)) * area)) + ((zlast == z) ? zlasti : (zlasti = (zlast = z) * width));
if (i != last + 1) {
raf.seek((HEADER_SIZE) + (i << 1));
mbb.position((HEADER_SIZE) + (i << 1));
}
last = i;
final int id = block.getId();
final int data = block.getData();
int combined = (id << 4) + data;
raf.writeChar(combined);
mbb.putChar((char) combined);
CompoundTag tile = block.getNbtData();
if (tile != null) {
setTile(x, y, z, tile);
@ -379,16 +417,20 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
@Override
public void setId(int i, int id) {
try {
int index;
if (i != last + 1) {
raf.seek((HEADER_SIZE) + (i << 1));
index = (HEADER_SIZE) + (i << 1);
} else {
index = mbb.position();
}
last = i;
mbb.position(index + 1);
// 00000000 00000000
// [ id ]data
int id1 = raf.readCurrent();
raf.writeUnsafe(id >> 4);
int id2 = raf.readCurrent();
raf.writeUnsafe(((id & 0xFF) << 4) + (id2 & 0xFF));
byte id2 = mbb.get();
mbb.position(index);
mbb.put((byte) (id >> 4));
mbb.put((byte) (((id & 0xFF) << 4) + (id2 & 0xFF)));
} catch (Exception e) {
MainUtil.handleError(e);
}
@ -397,10 +439,10 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
public void setCombined(int i, int combined) {
try {
if (i != last + 1) {
raf.seek((HEADER_SIZE) + (i << 1));
mbb.position((HEADER_SIZE) + (i << 1));
}
last = i;
raf.writeChar(combined);
mbb.putChar((char) combined);
} catch (Exception e) {
MainUtil.handleError(e);
}
@ -409,15 +451,14 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
@Override
public void setAdd(int i, int add) {
try {
if (i != last + 1) {
raf.seek((HEADER_SIZE) + (i << 1));
}
last = i;
int index = (HEADER_SIZE) + (i << 1);
mbb.position(index);
// 00000000 00000000
// [ id ]data
int id = (raf.readCurrent() & 0xFF);
raf.writeUnsafe(id + (add >> 4));
raf.read1();
char combined = mbb.getChar();
mbb.position(index);
mbb.putChar((char) ((combined & 0xFFFF) + (add << 12)));
} catch (Exception e) {
MainUtil.handleError(e);
}
@ -426,17 +467,17 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
@Override
public void setData(int i, int data) {
try {
int index;
if (i != last + 1) {
raf.seek((HEADER_SIZE) + (i << 1) + 1);
index = (HEADER_SIZE) + (i << 1) + 1;
} else {
raf.seek(raf.getFilePointer() + 1);
index = mbb.position() + 1;
}
mbb.position(index);
last = i;
// 00000000 00000000
// [ id ]data
// int skip = raf.read1();
int id2 = raf.readCurrent();
raf.writeUnsafe((id2 & 0xF0) + data);
byte id = mbb.get();
mbb.position(index);
mbb.put((byte) ((id & 0xF0) + data));
} catch (Exception e) {
MainUtil.handleError(e);
}

View File

@ -94,6 +94,10 @@ public abstract class FaweClipboard {
return tiles;
}
public void close() {}
public void flush() {}
/**
* Stores entity data.
*/

View File

@ -34,4 +34,11 @@ public class MultiClipboardHolder extends ClipboardHolder{
public void setTransform(Transform transform) {
holder.setTransform(transform);
}
@Override
public void close() {
for (ClipboardHolder holder : holders) {
if (holder != null) holder.close();
}
}
}

View File

@ -1,12 +0,0 @@
package com.boydti.fawe.object.clipboard;
import com.google.common.io.ByteSource;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.world.registry.WorldData;
import java.util.UUID;
public class RandomClipboardHolder extends LazyClipboardHolder {
public RandomClipboardHolder(ByteSource source, ClipboardFormat format, WorldData worldData, UUID uuid) {
super(source, format, worldData, uuid);
}
}

View File

@ -28,7 +28,6 @@ import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RunnableVal2;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.boydti.fawe.object.changeset.FaweChangeSet;
import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
import com.boydti.fawe.object.extent.ResettableExtent;
import com.boydti.fawe.util.EditSessionBuilder;
import com.boydti.fawe.util.MainUtil;
@ -45,8 +44,6 @@ 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;
@ -794,14 +791,8 @@ 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();
}
}
if (this.clipboard != null) {
this.clipboard.close();
}
this.clipboard = clipboard;
}

View File

@ -112,6 +112,7 @@ public class ClipboardCommands {
if (volume >= limit.MAX_CHECKS) {
throw new FaweException(BBC.WORLDEDIT_CANCEL_REASON_MAX_CHECKS);
}
session.setClipboard(null);
final Vector origin = region.getMinimumPoint();
final int mx = origin.getBlockX();
final int my = origin.getBlockY();
@ -149,7 +150,7 @@ public class ClipboardCommands {
if (volume >= limit.MAX_CHECKS) {
throw new FaweException(BBC.WORLDEDIT_CANCEL_REASON_MAX_CHECKS);
}
session.setClipboard(null);
BlockArrayClipboard clipboard = new BlockArrayClipboard(region, player.getUniqueId());
clipboard.setOrigin(session.getPlacementPosition(player));
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
@ -193,6 +194,7 @@ public class ClipboardCommands {
if (volume >= limit.MAX_CHANGES) {
throw new FaweException(BBC.WORLDEDIT_CANCEL_REASON_MAX_CHANGES);
}
session.setClipboard(null);
final Vector origin = region.getMinimumPoint();
final int mx = origin.getBlockX();
final int my = origin.getBlockY();
@ -232,6 +234,7 @@ public class ClipboardCommands {
if (volume >= limit.MAX_CHANGES) {
throw new FaweException(BBC.WORLDEDIT_CANCEL_REASON_MAX_CHANGES);
}
session.setClipboard(null);
BlockArrayClipboard clipboard = new BlockArrayClipboard(region, player.getUniqueId());
clipboard.setOrigin(session.getPlacementPosition(player));
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());

View File

@ -103,6 +103,7 @@ public class SchematicCommands {
}
try {
WorldData wd = player.getWorld().getWorldData();
session.setClipboard(null);
ClipboardHolder[] all = format.loadAllFromInput(player, wd, filename, true);
if (all != null) {
MultiClipboardHolder multi = new MultiClipboardHolder(wd, all);
@ -177,6 +178,7 @@ public class SchematicCommands {
final ClipboardReader reader = format.getReader(in);
final WorldData worldData = player.getWorld().getWorldData();
final Clipboard clipboard;
session.setClipboard(null);
if (reader instanceof SchematicReader) {
clipboard = ((SchematicReader) reader).read(player.getWorld().getWorldData(), player.getUniqueId());
} else if (reader instanceof StructureFormat) {

View File

@ -115,6 +115,10 @@ public class BlockArrayClipboard implements Clipboard, LightingExtent {
this.mz = origin.getBlockZ();
}
public void close() {
IMP.close();
}
@Override
public Region getRegion() {
return region.clone();

View File

@ -0,0 +1,112 @@
/*
* 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.session;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.math.transform.Identity;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.world.registry.WorldData;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Holds the clipboard and the current transform on the clipboard.
*/
public class ClipboardHolder {
private final WorldData worldData;
private final Clipboard clipboard;
private Transform transform = new Identity();
/**
* Create a new instance with the given clipboard.
*
* @param clipboard the clipboard
* @param worldData the mapping of blocks, entities, and so on
*/
public ClipboardHolder(Clipboard clipboard, WorldData worldData) {
checkNotNull(clipboard);
checkNotNull(worldData);
this.clipboard = clipboard;
this.worldData = worldData;
}
/**
* Get the mapping used for blocks, entities, and so on.
*
* @return the mapping
*/
public WorldData getWorldData() {
return worldData;
}
/**
* Get the clipboard.
* <p>
* If there is a transformation applied, the returned clipboard will
* not contain its effect.
*
* @return the clipboard
*/
public Clipboard getClipboard() {
return clipboard;
}
/**
* Set the transform.
*
* @param transform the transform
*/
public void setTransform(Transform transform) {
checkNotNull(transform);
this.transform = transform;
}
/**
* Get the transform.
*
* @return the transform
*/
public Transform getTransform() {
return transform;
}
/**
* Create a builder for an operation to paste this clipboard.
*
* @return a builder
*/
public PasteBuilder createPaste(Extent targetExtent, WorldData targetWorldData) {
return new PasteBuilder(this, targetExtent, targetWorldData);
}
public void close() {
if (clipboard instanceof BlockArrayClipboard) {
((BlockArrayClipboard) clipboard).close();
}
}
public static Class<?> inject() {
return ClipboardHolder.class;
}
}

View File

@ -221,6 +221,7 @@ public class SessionManager {
private void save(SessionHolder holder) {
SessionKey key = holder.key;
holder.session.setClipboard(null);
if (key.isPersistent()) {
try {
if (holder.session.compareAndResetDirty()) {