Remove storage buffers

This commit is contained in:
Luck 2018-04-29 11:33:51 +01:00
parent 7d089cb5f0
commit 2e136666be
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
12 changed files with 49 additions and 353 deletions

View File

@ -211,13 +211,18 @@ public class MigrationZPermissions extends SubCommand<Object> {
for (EntityMetadata metadata : entity.getMetadata()) {
String key = metadata.getName().toLowerCase();
if (key.isEmpty() || metadata.getStringValue().isEmpty()) continue;
Object value = metadata.getValue();
if (key.isEmpty() || value == null) continue;
String valueString = value.toString();
if (valueString.isEmpty()) continue;
if (key.equals(NodeFactory.PREFIX_KEY) || key.equals(NodeFactory.SUFFIX_KEY)) {
ChatMetaType type = ChatMetaType.valueOf(key.toUpperCase());
holder.setPermission(NodeFactory.buildChatMetaNode(type, weight, metadata.getStringValue()).build());
holder.setPermission(NodeFactory.buildChatMetaNode(type, weight, valueString).build());
} else {
holder.setPermission(NodeFactory.buildMetaNode(key, metadata.getStringValue()).build());
holder.setPermission(NodeFactory.buildMetaNode(key, valueString).build());
}
}
}

View File

@ -60,7 +60,7 @@ public class ApiGroupManager extends ApiAbstractManager<Group, me.lucko.luckperm
@Override
public CompletableFuture<me.lucko.luckperms.api.Group> createAndLoadGroup(@Nonnull String name) {
name = ApiUtils.checkName(Objects.requireNonNull(name, "name"));
return this.plugin.getStorage().noBuffer().createAndLoadGroup(name, CreationCause.API)
return this.plugin.getStorage().createAndLoadGroup(name, CreationCause.API)
.thenApply(this::getDelegateFor);
}
@ -68,14 +68,14 @@ public class ApiGroupManager extends ApiAbstractManager<Group, me.lucko.luckperm
@Override
public CompletableFuture<Optional<me.lucko.luckperms.api.Group>> loadGroup(@Nonnull String name) {
name = ApiUtils.checkName(Objects.requireNonNull(name, "name"));
return this.plugin.getStorage().noBuffer().loadGroup(name).thenApply(opt -> opt.map(this::getDelegateFor));
return this.plugin.getStorage().loadGroup(name).thenApply(opt -> opt.map(this::getDelegateFor));
}
@Nonnull
@Override
public CompletableFuture<Void> saveGroup(@Nonnull me.lucko.luckperms.api.Group group) {
Objects.requireNonNull(group, "group");
return this.plugin.getStorage().noBuffer().saveGroup(ApiGroup.cast(group));
return this.plugin.getStorage().saveGroup(ApiGroup.cast(group));
}
@Nonnull
@ -85,13 +85,13 @@ public class ApiGroupManager extends ApiAbstractManager<Group, me.lucko.luckperm
if (group.getName().equalsIgnoreCase(NodeFactory.DEFAULT_GROUP_NAME)) {
throw new IllegalArgumentException("Cannot delete the default group.");
}
return this.plugin.getStorage().noBuffer().deleteGroup(ApiGroup.cast(group), DeletionCause.API);
return this.plugin.getStorage().deleteGroup(ApiGroup.cast(group), DeletionCause.API);
}
@Nonnull
@Override
public CompletableFuture<Void> loadAllGroups() {
return this.plugin.getStorage().noBuffer().loadAllGroups();
return this.plugin.getStorage().loadAllGroups();
}
@Override

View File

@ -59,7 +59,7 @@ public class ApiTrackManager extends ApiAbstractManager<Track, me.lucko.luckperm
@Override
public CompletableFuture<me.lucko.luckperms.api.Track> createAndLoadTrack(@Nonnull String name) {
name = ApiUtils.checkName(Objects.requireNonNull(name, "name"));
return this.plugin.getStorage().noBuffer().createAndLoadTrack(name, CreationCause.API)
return this.plugin.getStorage().createAndLoadTrack(name, CreationCause.API)
.thenApply(this::getDelegateFor);
}
@ -67,27 +67,27 @@ public class ApiTrackManager extends ApiAbstractManager<Track, me.lucko.luckperm
@Override
public CompletableFuture<Optional<me.lucko.luckperms.api.Track>> loadTrack(@Nonnull String name) {
name = ApiUtils.checkName(Objects.requireNonNull(name, "name"));
return this.plugin.getStorage().noBuffer().loadTrack(name).thenApply(opt -> opt.map(this::getDelegateFor));
return this.plugin.getStorage().loadTrack(name).thenApply(opt -> opt.map(this::getDelegateFor));
}
@Nonnull
@Override
public CompletableFuture<Void> saveTrack(@Nonnull me.lucko.luckperms.api.Track track) {
Objects.requireNonNull(track, "track");
return this.plugin.getStorage().noBuffer().saveTrack(ApiTrack.cast(track));
return this.plugin.getStorage().saveTrack(ApiTrack.cast(track));
}
@Nonnull
@Override
public CompletableFuture<Void> deleteTrack(@Nonnull me.lucko.luckperms.api.Track track) {
Objects.requireNonNull(track, "track");
return this.plugin.getStorage().noBuffer().deleteTrack(ApiTrack.cast(track), DeletionCause.API);
return this.plugin.getStorage().deleteTrack(ApiTrack.cast(track), DeletionCause.API);
}
@Nonnull
@Override
public CompletableFuture<Void> loadAllTracks() {
return this.plugin.getStorage().noBuffer().loadAllTracks();
return this.plugin.getStorage().loadAllTracks();
}
@Override

View File

@ -64,7 +64,7 @@ public class ApiUserManager extends ApiAbstractManager<User, me.lucko.luckperms.
this.plugin.getUserManager().getHouseKeeper().registerApiUsage(uuid);
}
return this.plugin.getStorage().noBuffer().loadUser(uuid, username)
return this.plugin.getStorage().loadUser(uuid, username)
.thenApply(this::getDelegateFor);
}
@ -72,7 +72,7 @@ public class ApiUserManager extends ApiAbstractManager<User, me.lucko.luckperms.
@Override
public CompletableFuture<Void> saveUser(@Nonnull me.lucko.luckperms.api.User user) {
Objects.requireNonNull(user, "user");
return this.plugin.getStorage().noBuffer().saveUser(ApiUser.cast(user));
return this.plugin.getStorage().saveUser(ApiUser.cast(user));
}
@Override

View File

@ -52,7 +52,7 @@ public class ApiActionLogger implements ActionLogger {
@Nonnull
@Override
public CompletableFuture<Log> getLog() {
return this.plugin.getStorage().noBuffer().getLog().thenApply(ApiLog::new);
return this.plugin.getStorage().getLog().thenApply(ApiLog::new);
}
@Nonnull
@ -64,7 +64,7 @@ public class ApiActionLogger implements ActionLogger {
@Nonnull
@Override
public CompletableFuture<Void> submitToStorage(@Nonnull LogEntry entry) {
return this.plugin.getStorage().noBuffer().logAction(entry);
return this.plugin.getStorage().logAction(entry);
}
@Nonnull

View File

@ -103,7 +103,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Override
public CompletableFuture<Boolean> logAction(@Nonnull LogEntry entry) {
Objects.requireNonNull(entry, "entry");
return this.handle.noBuffer().logAction(entry)
return this.handle.logAction(entry)
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -111,7 +111,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Nonnull
@Override
public CompletableFuture<Log> getLog() {
return this.handle.noBuffer().getLog().<Log>thenApply(ApiLog::new).exceptionally(consumeExceptionToNull());
return this.handle.getLog().<Log>thenApply(ApiLog::new).exceptionally(consumeExceptionToNull());
}
@Nonnull
@ -124,7 +124,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
this.plugin.getUserManager().getHouseKeeper().registerApiUsage(uuid);
}
return this.handle.noBuffer().loadUser(uuid, username)
return this.handle.loadUser(uuid, username)
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -133,7 +133,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Override
public CompletableFuture<Boolean> saveUser(@Nonnull User user) {
Objects.requireNonNull(user, "user");
return this.handle.noBuffer().saveUser(ApiUser.cast(user))
return this.handle.saveUser(ApiUser.cast(user))
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -141,21 +141,21 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Nonnull
@Override
public CompletableFuture<Set<UUID>> getUniqueUsers() {
return this.handle.noBuffer().getUniqueUsers().exceptionally(consumeExceptionToNull());
return this.handle.getUniqueUsers().exceptionally(consumeExceptionToNull());
}
@Nonnull
@Override
public CompletableFuture<List<HeldPermission<UUID>>> getUsersWithPermission(@Nonnull String permission) {
Objects.requireNonNull(permission, "permission");
return this.handle.noBuffer().getUsersWithPermission(permission).exceptionally(consumeExceptionToNull());
return this.handle.getUsersWithPermission(permission).exceptionally(consumeExceptionToNull());
}
@Nonnull
@Override
public CompletableFuture<Boolean> createAndLoadGroup(@Nonnull String name) {
Objects.requireNonNull(name, "name");
return this.handle.noBuffer().createAndLoadGroup(checkName(name), CreationCause.API)
return this.handle.createAndLoadGroup(checkName(name), CreationCause.API)
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -164,7 +164,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Override
public CompletableFuture<Boolean> loadGroup(@Nonnull String name) {
Objects.requireNonNull(name, "name");
return this.handle.noBuffer().loadGroup(checkName(name))
return this.handle.loadGroup(checkName(name))
.thenApply(Optional::isPresent)
.exceptionally(consumeExceptionToFalse());
}
@ -172,7 +172,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Nonnull
@Override
public CompletableFuture<Boolean> loadAllGroups() {
return this.handle.noBuffer().loadAllGroups()
return this.handle.loadAllGroups()
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -181,7 +181,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Override
public CompletableFuture<Boolean> saveGroup(@Nonnull Group group) {
Objects.requireNonNull(group, "group");
return this.handle.noBuffer().saveGroup(ApiGroup.cast(group))
return this.handle.saveGroup(ApiGroup.cast(group))
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -193,7 +193,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
if (group.getName().equalsIgnoreCase(NodeFactory.DEFAULT_GROUP_NAME)) {
throw new IllegalArgumentException("Cannot delete the default group.");
}
return this.handle.noBuffer().deleteGroup(ApiGroup.cast(group), DeletionCause.API)
return this.handle.deleteGroup(ApiGroup.cast(group), DeletionCause.API)
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -202,14 +202,14 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Override
public CompletableFuture<List<HeldPermission<String>>> getGroupsWithPermission(@Nonnull String permission) {
Objects.requireNonNull(permission, "permission");
return this.handle.noBuffer().getGroupsWithPermission(permission).exceptionally(consumeExceptionToNull());
return this.handle.getGroupsWithPermission(permission).exceptionally(consumeExceptionToNull());
}
@Nonnull
@Override
public CompletableFuture<Boolean> createAndLoadTrack(@Nonnull String name) {
Objects.requireNonNull(name, "name");
return this.handle.noBuffer().createAndLoadTrack(checkName(name), CreationCause.API)
return this.handle.createAndLoadTrack(checkName(name), CreationCause.API)
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -218,7 +218,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Override
public CompletableFuture<Boolean> loadTrack(@Nonnull String name) {
Objects.requireNonNull(name, "name");
return this.handle.noBuffer().loadTrack(checkName(name))
return this.handle.loadTrack(checkName(name))
.thenApply(Optional::isPresent)
.exceptionally(consumeExceptionToFalse());
}
@ -226,7 +226,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Nonnull
@Override
public CompletableFuture<Boolean> loadAllTracks() {
return this.handle.noBuffer().loadAllTracks()
return this.handle.loadAllTracks()
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -235,7 +235,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Override
public CompletableFuture<Boolean> saveTrack(@Nonnull Track track) {
Objects.requireNonNull(track, "track");
return this.handle.noBuffer().saveTrack(ApiTrack.cast(track))
return this.handle.saveTrack(ApiTrack.cast(track))
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -244,7 +244,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Override
public CompletableFuture<Boolean> deleteTrack(@Nonnull Track track) {
Objects.requireNonNull(track, "track");
return this.handle.noBuffer().deleteTrack(ApiTrack.cast(track), DeletionCause.API)
return this.handle.deleteTrack(ApiTrack.cast(track), DeletionCause.API)
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -254,7 +254,7 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
public CompletableFuture<Boolean> saveUUIDData(@Nonnull String username, @Nonnull UUID uuid) {
Objects.requireNonNull(username, "username");
Objects.requireNonNull(uuid, "uuid");
return this.handle.noBuffer().savePlayerData(uuid, checkUsername(username))
return this.handle.savePlayerData(uuid, checkUsername(username))
.thenApply(r -> true)
.exceptionally(consumeExceptionToFalse());
}
@ -263,13 +263,13 @@ public class ApiStorage implements me.lucko.luckperms.api.Storage {
@Override
public CompletableFuture<UUID> getUUID(@Nonnull String username) {
Objects.requireNonNull(username, "username");
return this.handle.noBuffer().getPlayerUuid(checkUsername(username));
return this.handle.getPlayerUuid(checkUsername(username));
}
@Nonnull
@Override
public CompletableFuture<String> getName(@Nonnull UUID uuid) {
Objects.requireNonNull(uuid, "uuid");
return this.handle.noBuffer().getPlayerName(uuid);
return this.handle.getPlayerName(uuid);
}
}

View File

@ -1,164 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.buffers;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
/**
* Thread-safe buffer utility. Holds a buffer of objects to be processed after they've been waiting in the buffer
* for a given time. If the same object is pushed to the buffer again in that time, its wait time is reset.
*
* @param <T> the type of objects in the buffer
* @param <R> the type of result produced by the final process
*/
public class Buffer<T, R> implements Runnable {
private static final long DEFAULT_FLUSH_TIME = 1000; // 1 second
public static <T, R> Buffer<T, R> of(Function<T, R> dequeueFunc) {
return new Buffer<>(dequeueFunc);
}
private final ReentrantLock lock = new ReentrantLock();
private final List<BufferedObject<T, R>> buffer = new LinkedList<>();
private final Function<T, R> dequeueFunc;
private Buffer(Function<T, R> dequeueFunc) {
this.dequeueFunc = dequeueFunc;
}
public CompletableFuture<R> enqueue(T object) {
Objects.requireNonNull(object, "object");
this.lock.lock();
try {
ListIterator<BufferedObject<T, R>> it = this.buffer.listIterator();
BufferedObject<T, R> o = null;
while (it.hasNext()) {
BufferedObject<T, R> obj = it.next();
if (obj.getObject().equals(object)) {
o = obj;
it.remove();
break;
}
}
if (o == null) {
o = new BufferedObject<>(System.currentTimeMillis(), object, new CompletableFuture<R>());
} else {
o.setBufferTime(System.currentTimeMillis());
}
this.buffer.add(o);
return o.getFuture();
} finally {
this.lock.unlock();
}
}
protected R dequeue(T t) {
return this.dequeueFunc.apply(t);
}
public void flush(long flushTime) {
long time = System.currentTimeMillis();
this.lock.lock();
try {
ListIterator<BufferedObject<T, R>> it = this.buffer.listIterator(this.buffer.size());
while (it.hasPrevious()) {
BufferedObject<T, R> obj = it.previous();
long bufferedTime = time - obj.getBufferTime();
if (bufferedTime > flushTime) {
// Flush
R r = dequeue(obj.getObject());
obj.getFuture().complete(r);
it.remove();
}
}
} finally {
this.lock.unlock();
}
}
@Override
public void run() {
flush(DEFAULT_FLUSH_TIME);
}
private static final class BufferedObject<T, R> {
private long bufferTime;
private final T object;
private final CompletableFuture<R> future;
public BufferedObject(long bufferTime, T object, CompletableFuture<R> future) {
this.bufferTime = bufferTime;
this.object = object;
this.future = future;
}
public long getBufferTime() {
return this.bufferTime;
}
public void setBufferTime(long bufferTime) {
this.bufferTime = bufferTime;
}
public T getObject() {
return this.object;
}
public CompletableFuture<R> getFuture() {
return this.future;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Buffer.BufferedObject)) return false;
final BufferedObject that = (BufferedObject) o;
return Objects.equals(this.getObject(), that.getObject());
}
@Override
public int hashCode() {
return Objects.hashCode(this.object);
}
}
}

View File

@ -101,7 +101,7 @@ public final class StorageAssistant {
}
try {
plugin.getStorage().noBuffer().saveUser(user).get();
plugin.getStorage().saveUser(user).get();
} catch (Exception e) {
e.printStackTrace();
Message.USER_SAVE_ERROR.send(sender, user.getFriendlyName());
@ -125,7 +125,7 @@ public final class StorageAssistant {
}
try {
plugin.getStorage().noBuffer().saveGroup(group).get();
plugin.getStorage().saveGroup(group).get();
} catch (Exception e) {
e.printStackTrace();
Message.GROUP_SAVE_ERROR.send(sender, group.getFriendlyName());
@ -149,7 +149,7 @@ public final class StorageAssistant {
}
try {
plugin.getStorage().noBuffer().saveTrack(track).get();
plugin.getStorage().saveTrack(track).get();
} catch (Exception e) {
e.printStackTrace();
Message.TRACK_SAVE_ERROR.send(sender, track.getName());

View File

@ -77,7 +77,7 @@ public class LogNotify extends SubCommand<Log> {
user.removeIf(n -> n.getPermission().equalsIgnoreCase("luckperms.log.notify.ignoring"));
}
plugin.getStorage().noBuffer().saveUser(user).join();
plugin.getStorage().saveUser(user).join();
}
@Override

View File

@ -73,7 +73,7 @@ public abstract class AbstractConnectionListener implements ConnectionListener {
this.plugin.getLogger().warn("This is usually because the server is not authenticating correctly. If you're using BungeeCord, please ensure that IP-Forwarding is setup correctly!");
}
User user = this.plugin.getStorage().noBuffer().loadUser(u, username).join();
User user = this.plugin.getStorage().loadUser(u, username).join();
if (user == null) {
throw new NullPointerException("User is null");
} else {
@ -87,7 +87,7 @@ public abstract class AbstractConnectionListener implements ConnectionListener {
// If they were given a default, persist the new assignments back to the storage.
if (save) {
this.plugin.getStorage().noBuffer().saveUser(user).join();
this.plugin.getStorage().saveUser(user).join();
}
// Does some minimum pre-calculations to (maybe) speed things up later.

View File

@ -40,7 +40,6 @@ import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.dao.AbstractDao;
import me.lucko.luckperms.common.storage.wrappings.BufferedOutputStorage;
import me.lucko.luckperms.common.storage.wrappings.PhasedStorage;
import java.util.List;
@ -60,11 +59,7 @@ public class AbstractStorage implements Storage {
// make a base implementation
Storage base = new AbstractStorage(plugin, backing);
// wrap with a phaser
PhasedStorage phased = PhasedStorage.wrap(base);
// wrap with a buffer
BufferedOutputStorage buffered = BufferedOutputStorage.wrap(phased, 250L);
plugin.getBootstrap().getScheduler().asyncRepeating(buffered.buffer(), 2L);
return buffered;
return PhasedStorage.wrap(base);
}
private final LuckPermsPlugin plugin;

View File

@ -1,140 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.wrappings;
import me.lucko.luckperms.common.buffers.Buffer;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.storage.Storage;
import java.lang.reflect.Proxy;
import java.util.concurrent.CompletableFuture;
/**
* A storage wrapping that passes save tasks through a buffer
*/
public interface BufferedOutputStorage extends Storage {
/**
* Creates a new instance of {@link BufferedOutputStorage} which delegates called to the given
* {@link Storage} instance.
*
* @param delegate the delegate storage impl
* @param flushTime default flush time for the buffer. See {@link Buffer#flush(long)}
* @return the new buffered storage instance
*/
static BufferedOutputStorage wrap(Storage delegate, long flushTime) {
// create a buffer handler - we pass the unwrapped delegate here.
StorageBuffer buffer = new StorageBuffer(delegate, flushTime);
// create and return a proxy instance which directs save calls through the buffer
return (BufferedOutputStorage) Proxy.newProxyInstance(
BufferedOutputStorage.class.getClassLoader(),
new Class[]{BufferedOutputStorage.class},
(proxy, method, args) -> {
// run save methods through the buffer instance
switch (method.getName()) {
case "saveUser":
return buffer.saveUser((User) args[0]);
case "saveGroup":
return buffer.saveGroup((Group) args[0]);
case "saveTrack":
return buffer.saveTrack((Track) args[0]);
}
// provide implementation of #noBuffer
if (method.getName().equals("noBuffer")) {
return delegate;
}
// provide implementation of #buffer
if (method.getName().equals("buffer")) {
return buffer;
}
// flush the buffer on shutdown
if (method.getName().equals("shutdown")) {
buffer.forceFlush();
// ...and then delegate
}
// delegate the call
return method.invoke(delegate, args);
}
);
}
/**
* Gets the buffer behind this instance
*
* @return the buffer
*/
StorageBuffer buffer();
final class StorageBuffer implements Runnable {
private final long flushTime;
private final Buffer<User, Void> userOutputBuffer;
private final Buffer<Group, Void> groupOutputBuffer;
private final Buffer<Track, Void> trackOutputBuffer;
private StorageBuffer(Storage delegate, long flushTime) {
this.flushTime = flushTime;
this.userOutputBuffer = Buffer.of(user -> delegate.saveUser(user).join());
this.groupOutputBuffer = Buffer.of(group -> delegate.saveGroup(group).join());
this.trackOutputBuffer = Buffer.of(track -> delegate.saveTrack(track).join());
}
public void run() {
flush(this.flushTime);
}
public void forceFlush() {
flush(-1);
}
public void flush(long flushTime) {
this.userOutputBuffer.flush(flushTime);
this.groupOutputBuffer.flush(flushTime);
this.trackOutputBuffer.flush(flushTime);
}
// copy the required implementation methods from the Storage interface
private CompletableFuture<Void> saveUser(User user) {
return this.userOutputBuffer.enqueue(user);
}
private CompletableFuture<Void> saveGroup(Group group) {
return this.groupOutputBuffer.enqueue(group);
}
private CompletableFuture<Void> saveTrack(Track track) {
return this.trackOutputBuffer.enqueue(track);
}
}
}