Archived
0

Tweak some command stuff, clear/swap entities

This commit is contained in:
Dico Karssiens
2018-11-17 21:32:43 +00:00
parent 0f196f59c6
commit 5ef2584fdb
25 changed files with 3953 additions and 3554 deletions

View File

@@ -13,7 +13,7 @@ version = "0.2"
plugins { plugins {
java java
kotlin("jvm") version "1.3.0-rc-146" kotlin("jvm") version "1.3.0"
id("com.github.johnrengelman.plugin-shadow") version "2.0.3" id("com.github.johnrengelman.plugin-shadow") version "2.0.3"
} }
@@ -30,10 +30,11 @@ allprojects {
maven("https://dl.bintray.com/kotlin/kotlin-dev/") maven("https://dl.bintray.com/kotlin/kotlin-dev/")
maven("https://dl.bintray.com/kotlin/kotlin-eap/") maven("https://dl.bintray.com/kotlin/kotlin-eap/")
maven("https://dl.bintray.com/kotlin/kotlinx/") maven("https://dl.bintray.com/kotlin/kotlinx/")
maven("http://maven.sk89q.com/repo")
} }
dependencies { dependencies {
val spigotVersion = "1.13.1-R0.1-SNAPSHOT" val spigotVersion = "1.13.2-R0.1-SNAPSHOT"
c.provided("org.bukkit:bukkit:$spigotVersion") { isTransitive = false } c.provided("org.bukkit:bukkit:$spigotVersion") { isTransitive = false }
c.provided("org.spigotmc:spigot-api:$spigotVersion") { isTransitive = false } c.provided("org.spigotmc:spigot-api:$spigotVersion") { isTransitive = false }
@@ -52,13 +53,15 @@ project(":dicore3:dicore3-core") {
} }
} }
val coroutinesCore = kotlinx("coroutines-core:0.26.1-eap13")
project(":dicore3:dicore3-command") { project(":dicore3:dicore3-command") {
apply<KotlinPlatformJvmPlugin>() apply<KotlinPlatformJvmPlugin>()
dependencies { dependencies {
c.kotlinStd(kotlin("stdlib-jdk8")) c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect")) c.kotlinStd(kotlin("reflect"))
c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13")) c.kotlinStd(coroutinesCore)
compile(project(":dicore3:dicore3-core")) compile(project(":dicore3:dicore3-core"))
compile("com.thoughtworks.paranamer:paranamer:2.8") compile("com.thoughtworks.paranamer:paranamer:2.8")
@@ -72,12 +75,13 @@ dependencies {
c.kotlinStd(kotlin("stdlib-jdk8")) c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect")) c.kotlinStd(kotlin("reflect"))
c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13")) c.kotlinStd(coroutinesCore)
c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.7-eap13") c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.12")
// not on sk89q maven repo yet // not on sk89q maven repo yet
compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar")) //compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar"))
compileClasspath(files("$rootDir/debug/lib/spigot-1.13.1.jar")) // compileClasspath(files("$rootDir/debug/lib/spigot-1.13.2.jar"))
compileClasspath("com.sk89q.worldedit:worldedit-bukkit:7.0.0-SNAPSHOT")
compile("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false } compile("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false }
compile("joda-time:joda-time:2.10") compile("joda-time:joda-time:2.10")
@@ -167,7 +171,6 @@ val ConfigurationContainer.`kotlinStd`: Configuration
get() = findByName("kotlinStd") ?: create("kotlinStd").let { compileClasspath.extendsFrom(it) } get() = findByName("kotlinStd") ?: create("kotlinStd").let { compileClasspath.extendsFrom(it) }
fun Jar.fromFiles(files: Iterable<File>) { fun Jar.fromFiles(files: Iterable<File>) {
return
afterEvaluate { from(*files.map { if (it.isDirectory) it else zipTree(it) }.toTypedArray()) } afterEvaluate { from(*files.map { if (it.isDirectory) it else zipTree(it) }.toTypedArray()) }
} }

View File

@@ -233,6 +233,7 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
@Override @Override
public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) { public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) {
ExecutionContext context = new ExecutionContext(sender, buffer, true); ExecutionContext context = new ExecutionContext(sender, buffer, true);
long start = System.currentTimeMillis();
try { try {
ICommandAddress target = getCommandTarget(context, buffer); ICommandAddress target = getCommandTarget(context, buffer);
@@ -269,6 +270,11 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
} catch (CommandException ex) { } catch (CommandException ex) {
return Collections.emptyList(); return Collections.emptyList();
} finally {
long duration = System.currentTimeMillis() - start;
if (duration > 2) {
System.out.println(String.format("Complete took %.3f seconds", duration / 1000.0));
}
} }
} }

View File

@@ -89,6 +89,8 @@ public class ContextParser {
m_curRepeatingList = null; m_curRepeatingList = null;
assignDefaultValuesToUncomputedParams(); assignDefaultValuesToUncomputedParams();
arrayifyRepeatedParamValue(); arrayifyRepeatedParamValue();
m_done = true;
} }
} }

View File

@@ -0,0 +1,186 @@
package io.dico.dicore.command.registration.reflect;
import io.dico.dicore.command.CommandException;
import io.dico.dicore.command.ExecutionContext;
import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier;
/**
* Call flags store which extra parameters the target function expects on top of command parameters.
* All 4 possible extra parameters are listed below.
* <p>
* Extra parameters are ordered by the bit that represents them in the call flags.
* They can either be leading or trailing the command's parameters.
*/
public class ReflectiveCallFlags {
/**
* Receiver ({@code this} in some kotlin functions - always first parameter)
*
* @see ICommandInterceptor#getReceiver(io.dico.dicore.command.ExecutionContext, java.lang.reflect.Method, String)
*/
public static final int RECEIVER_BIT = 1 << 0;
/**
* CommandSender
*
* @see org.bukkit.command.CommandSender
*/
public static final int SENDER_BIT = 1 << 1;
/**
* ExecutionContext
*
* @see io.dico.dicore.command.ExecutionContext
*/
public static final int CONTEXT_BIT = 1 << 2;
/**
* Continuation (trailing parameters of kotlin suspended functions)
*
* @see kotlin.coroutines.Continuation
*/
public static final int CONTINUATION_BIT = 1 << 3;
/**
* Mask of extra parameters that trail the command's parameters, instead of leading.
*/
public static final int TRAILING_MASK = CONTINUATION_BIT;
/**
* Check if the call arg is trailing the command's parameters.
*
* @param bit the bit used for the call flag
* @return true if the call arg is trailing the command's parameters
*/
public static boolean isTrailingCallArg(int bit) {
return (bit & TRAILING_MASK) != 0;
}
/**
* Number of call arguments leading the command parameters.
*
* @param flags the call flags
* @return the number of call arguments leading the command parameters
*/
public static int getLeadingCallArgNum(int flags) {
return Integer.bitCount(flags & ~TRAILING_MASK);
}
/**
* Number of call arguments trailing the command parameters.
*
* @param flags the call flags
* @return the number of call arguments trailing the command parameters
*/
public static int getTrailingCallArgNum(int flags) {
return Integer.bitCount(flags & TRAILING_MASK);
}
/**
* Check if the flags contain the call arg.
*
* @param flags the call flags
* @param bit the bit used for the call flag
* @return true if the flags contain the call arg
*/
public static boolean hasCallArg(int flags, int bit) {
return (flags & bit) != 0;
}
/**
* Get the index used for the call arg when calling the reflective function
*
* @param flags the call flags
* @param bit the bit used for the call flag
* @param cmdParameterNum the number of parameters of the command
* @return the index used for the call arg
*/
public static int getCallArgIndex(int flags, int bit, int cmdParameterNum) {
if ((bit & TRAILING_MASK) == 0) {
// Leading.
int preceding = precedingMaskFrom(bit);
int mask = flags & precedingMaskFrom(bit) & ~TRAILING_MASK;
// Count the number of present call args that are leading and precede the given bit
return Integer.bitCount(flags & precedingMaskFrom(bit) & ~TRAILING_MASK);
} else {
// Trailing.
// Count the number of present call args that are leading
// plus the number of present call args that are trailing and precede the given bit
// plus the command's parameters
return Integer.bitCount(flags & ~TRAILING_MASK)
+ Integer.bitCount(flags & precedingMaskFrom(bit) & TRAILING_MASK)
+ cmdParameterNum;
}
}
/**
* Get the mask for all bits trailing the given fromBit
*
* <p>
* For example, if the bit is 00010000
* This function returns 00001111
* <p>
*
* @param fromBit number with the bit set there the ones should stop.
* @return the mask for all bits trailing the given fromBit
*/
private static int precedingMaskFrom(int fromBit) {
int trailingZeros = Integer.numberOfTrailingZeros(fromBit);
if (trailingZeros == 0) return 0;
return -1 >>> -trailingZeros;
}
/**
* Get the object array used to call the function.
*
* @param callFlags the call flags
* @param context the context
* @param parameterOrder the order of parameters in the function
* @param receiverFunction the function that will create the receiver for this call, if applicable
* @return the call args
*/
public static Object[] getCallArgs(
int callFlags,
ExecutionContext context,
String[] parameterOrder,
CheckedSupplier<Object, CommandException> receiverFunction
) throws CommandException {
int leadingParameterNum = getLeadingCallArgNum(callFlags);
int cmdParameterNum = parameterOrder.length;
int trailingParameterNum = getTrailingCallArgNum(callFlags);
Object[] result = new Object[leadingParameterNum + cmdParameterNum + trailingParameterNum];
if (hasCallArg(callFlags, RECEIVER_BIT)) {
int index = getCallArgIndex(callFlags, RECEIVER_BIT, cmdParameterNum);
result[index] = receiverFunction.get();
}
if (hasCallArg(callFlags, SENDER_BIT)) {
int index = getCallArgIndex(callFlags, SENDER_BIT, cmdParameterNum);
result[index] = context.getSender();
}
if (hasCallArg(callFlags, CONTEXT_BIT)) {
int index = getCallArgIndex(callFlags, CONTEXT_BIT, cmdParameterNum);
result[index] = context;
}
if (hasCallArg(callFlags, CONTINUATION_BIT)) {
int index = getCallArgIndex(callFlags, CONTINUATION_BIT, cmdParameterNum);
result[index] = null; // filled in later.
}
for (int i = 0; i < parameterOrder.length; i++) {
String parameterName = parameterOrder[i];
result[leadingParameterNum + i] = context.get(parameterName);
}
return result;
}
}

View File

@@ -4,6 +4,8 @@ import io.dico.dicore.command.*;
import io.dico.dicore.command.annotation.Cmd; import io.dico.dicore.command.annotation.Cmd;
import io.dico.dicore.command.annotation.GenerateCommands; import io.dico.dicore.command.annotation.GenerateCommands;
import io.dico.dicore.command.parameter.type.IParameterTypeSelector; import io.dico.dicore.command.parameter.type.IParameterTypeSelector;
import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier;
import kotlin.coroutines.CoroutineContext;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@@ -11,14 +13,11 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
public final class ReflectiveCommand extends Command { public final class ReflectiveCommand extends Command {
private static final int continuationMask = 1 << 3;
private final Cmd cmdAnnotation; private final Cmd cmdAnnotation;
private final Method method; private final Method method;
private final Object instance; private final Object instance;
private String[] parameterOrder; private String[] parameterOrder;
private final int callFlags;
// hasContinuation | hasContext | hasSender | hasReceiver
private final int flags;
ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException { ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
if (!method.isAnnotationPresent(Cmd.class)) { if (!method.isAnnotationPresent(Cmd.class)) {
@@ -48,7 +47,7 @@ public final class ReflectiveCommand extends Command {
this.method = method; this.method = method;
this.instance = instance; this.instance = instance;
this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters); this.callFlags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
} }
public Method getMethod() { public Method getMethod() {
@@ -59,12 +58,22 @@ public final class ReflectiveCommand extends Command {
return instance; return instance;
} }
public String getCmdName() { return cmdAnnotation.value(); } public String getCmdName() {
return cmdAnnotation.value();
}
public int getCallFlags() {
return callFlags;
}
void setParameterOrder(String[] parameterOrder) { void setParameterOrder(String[] parameterOrder) {
this.parameterOrder = parameterOrder; this.parameterOrder = parameterOrder;
} }
public int getParameterNum() {
return parameterOrder.length;
}
ICommandAddress getAddress() { ICommandAddress getAddress() {
ChildCommandAddress result = new ChildCommandAddress(); ChildCommandAddress result = new ChildCommandAddress();
result.setCommand(this); result.setCommand(this);
@@ -86,54 +95,24 @@ public final class ReflectiveCommand extends Command {
@Override @Override
public String execute(CommandSender sender, ExecutionContext context) throws CommandException { public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
String[] parameterOrder = this.parameterOrder;
int extraArgumentCount = Integer.bitCount(flags);
int parameterStartIndex = Integer.bitCount(flags & ~continuationMask);
Object[] args = new Object[parameterOrder.length + extraArgumentCount]; CheckedSupplier<Object, CommandException> receiverFunction = () -> {
int i = 0;
int mask = 1;
if ((flags & mask) != 0) {
// Has receiver
try { try {
args[i++] = ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName()); return ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName());
} catch (Exception ex) { } catch (Exception ex) {
handleException(ex); handleException(ex);
return null; // unreachable return null; // unreachable
} }
};
Object[] callArgs = ReflectiveCallFlags.getCallArgs(callFlags, context, parameterOrder, receiverFunction);
if (ReflectiveCallFlags.hasCallArg(callFlags, ReflectiveCallFlags.CONTINUATION_BIT)) {
// If it has a continuation, call as coroutine
return callAsCoroutine(context, callArgs);
} }
mask <<= 1; return callSynchronously(callArgs);
if ((flags & mask) != 0) {
// Has sender
args[i++] = sender;
}
mask <<= 1;
if ((flags & mask) != 0) {
// Has context
args[i++] = context;
}
mask <<= 1;
if ((flags & mask) != 0) {
// Has continuation
extraArgumentCount--;
}
for (int n = args.length; i < n; i++) {
args[i] = context.get(parameterOrder[i - extraArgumentCount]);
}
if ((flags & mask) != 0) {
// Since it has continuation, call as coroutine
return callAsCoroutine(context, args);
}
return callSynchronously(args);
} }
private boolean isSuspendFunction() { private boolean isSuspendFunction() {
@@ -180,8 +159,11 @@ public final class ReflectiveCommand extends Command {
throw new CommandException("An internal error occurred while executing this command.", ex); throw new CommandException("An internal error occurred while executing this command.", ex);
} }
private String callAsCoroutine(ExecutionContext context, Object[] args) { private String callAsCoroutine(ExecutionContext executionContext, Object[] args) throws CommandException {
return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandInterceptor) instance, context, args); ICommandInterceptor factory = (ICommandInterceptor) instance;
CoroutineContext coroutineContext = (CoroutineContext) factory.getCoroutineContext(executionContext, method, getCmdName());
int continuationIndex = ReflectiveCallFlags.getCallArgIndex(callFlags, ReflectiveCallFlags.CONTINUATION_BIT, parameterOrder.length);
return KotlinReflectiveRegistrationKt.callCommandAsCoroutine(executionContext, coroutineContext, continuationIndex, method, instance, args);
} }
} }

View File

@@ -22,6 +22,8 @@ import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException; import java.util.regex.PatternSyntaxException;
import static io.dico.dicore.command.registration.reflect.ReflectiveCallFlags.*;
/** /**
* Takes care of turning a reflection {@link Method} into a command and more. * Takes care of turning a reflection {@link Method} into a command and more.
*/ */
@@ -196,47 +198,43 @@ public class ReflectiveRegistration {
return new ReflectiveCommand(selector, method, instance).getAddress(); return new ReflectiveCommand(selector, method, instance).getAddress();
} }
static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] parameters) throws CommandParseException { static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] callParameters) throws CommandParseException {
ParameterList list = command.getParameterList(); ParameterList list = command.getParameterList();
boolean hasReceiverParameter = false;
boolean hasSenderParameter = false;
boolean hasContextParameter = false;
boolean hasContinuationParameter = false;
int start = 0;
int end = parameters.length;
Class<?> senderParameterType = null; Class<?> senderParameterType = null;
int flags = 0;
int start = 0;
int end = callParameters.length;
if (parameters.length > start if (callParameters.length > start
&& command.getInstance() instanceof ICommandInterceptor && command.getInstance() instanceof ICommandInterceptor
&& ICommandReceiver.class.isAssignableFrom(parameters[start].getType())) { && ICommandReceiver.class.isAssignableFrom(callParameters[start].getType())) {
hasReceiverParameter = true; flags |= RECEIVER_BIT;
start++; ++start;
} }
if (parameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = parameters[start].getType())) { if (callParameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = callParameters[start].getType())) {
hasSenderParameter = true; flags |= SENDER_BIT;
start++; ++start;
} }
if (parameters.length > start && parameters[start].getType() == ExecutionContext.class) { if (callParameters.length > start && callParameters[start].getType() == ExecutionContext.class) {
hasContextParameter = true; flags |= CONTEXT_BIT;
start++; ++start;
} }
if (parameters.length > start && parameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) { if (callParameters.length > start && callParameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
hasContinuationParameter = true; flags |= CONTINUATION_BIT;
end--; --end;
} }
String[] parameterNames = lookupParameterNames(method, parameters, start); String[] parameterNames = lookupParameterNames(method, callParameters, start);
for (int i = start, n = end; i < n; i++) { for (int i = start, n = end; i < n; i++) {
Parameter<?, ?> parameter = parseParameter(selector, method, parameters[i], parameterNames[i - start]); Parameter<?, ?> parameter = parseParameter(selector, method, callParameters[i], parameterNames[i - start]);
list.addParameter(parameter); list.addParameter(parameter);
} }
command.setParameterOrder(hasContinuationParameter ? Arrays.copyOfRange(parameterNames, 0, parameterNames.length - 1) : parameterNames);
command.setParameterOrder(hasCallArg(flags, CONTINUATION_BIT) ? Arrays.copyOfRange(parameterNames, 0, parameterNames.length - 1) : parameterNames);
RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class); RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
if (cmdPermissions != null) { if (cmdPermissions != null) {
@@ -277,6 +275,7 @@ public class ReflectiveRegistration {
command.setDescription(); command.setDescription();
} }
boolean hasSenderParameter = hasCallArg(flags, SENDER_BIT);
if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) { if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
command.addContextFilter(IContextFilter.PLAYER_ONLY); command.addContextFilter(IContextFilter.PLAYER_ONLY);
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) { } else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
@@ -287,17 +286,9 @@ public class ReflectiveRegistration {
command.addContextFilter(IContextFilter.CONSOLE_ONLY); command.addContextFilter(IContextFilter.CONSOLE_ONLY);
} }
list.setRepeatFinalParameter(parameters.length > start && parameters[parameters.length - 1].isVarArgs()); list.setRepeatFinalParameter(callParameters.length > start && callParameters[callParameters.length - 1].isVarArgs());
list.setFinalParameterMayBeFlag(true); list.setFinalParameterMayBeFlag(true);
int flags = 0;
if (hasContinuationParameter) flags |= 1;
flags <<= 1;
if (hasContextParameter) flags |= 1;
flags <<= 1;
if (hasSenderParameter) flags |= 1;
flags <<= 1;
if (hasReceiverParameter) flags |= 1;
return flags; return flags;
} }

View File

@@ -17,20 +17,23 @@ fun isSuspendFunction(method: Method): Boolean {
return func.isSuspend return func.isSuspend
} }
fun callAsCoroutine( @Throws(CommandException::class)
command: ReflectiveCommand, fun callCommandAsCoroutine(
factory: ICommandInterceptor, executionContext: ExecutionContext,
context: ExecutionContext, coroutineContext: CoroutineContext,
continuationIndex: Int,
method: Method,
instance: Any?,
args: Array<Any?> args: Array<Any?>
): String? { ): String? {
val coroutineContext = factory.getCoroutineContext(context, command.method, command.cmdName) as CoroutineContext
// UNDISPATCHED causes the handler to run until the first suspension point on the current thread, // UNDISPATCHED causes the handler to run until the first suspension point on the current thread,
// meaning command handlers that don't have suspension points will run completely synchronously. // meaning command handlers that don't have suspension points will run completely synchronously.
// Tasks that take time to compute should suspend the coroutine and resume on another thread. // Tasks that take time to compute should suspend the coroutine and resume on another thread.
val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) { val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) {
suspendCoroutineUninterceptedOrReturn<Any?> { cont -> suspendCoroutineUninterceptedOrReturn<Any?> { cont ->
command.method.invoke(command.instance, *args, cont.intercepted()) args[continuationIndex] = cont.intercepted()
method.invoke(instance, *args)
} }
} }
@@ -39,12 +42,12 @@ fun callAsCoroutine(
} }
job.invokeOnCompletion { job.invokeOnCompletion {
val chatHandler = context.address.chatHandler val chatHandler = executionContext.address.chatHandler
try { try {
val result = job.getResult() val result = job.getResult()
chatHandler.sendMessage(context.sender, EMessageType.RESULT, result) chatHandler.sendMessage(executionContext.sender, EMessageType.RESULT, result)
} catch (ex: Throwable) { } catch (ex: Throwable) {
chatHandler.handleException(context.sender, context, ex) chatHandler.handleException(executionContext.sender, executionContext, ex)
} }
} }

View File

@@ -1,6 +1,8 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.util.PluginAware
import io.dico.parcels2.util.math.clampMin import io.dico.parcels2.util.math.clampMin
import io.dico.parcels2.util.scheduleRepeating
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart.LAZY import kotlinx.coroutines.CoroutineStart.LAZY
@@ -74,7 +76,12 @@ interface Job : JobAndScopeMembersUnion {
* *
* if [asCompletionListener] is true, [onCompleted] is called with the same [block] * if [asCompletionListener] is true, [onCompleted] is called with the same [block]
*/ */
fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean = true, block: JobUpdateLister): Job fun onProgressUpdate(
minDelay: Int,
minInterval: Int,
asCompletionListener: Boolean = true,
block: JobUpdateLister
): Job
/** /**
* Calls the given [block] when this job completes, with the progress value 1.0. * Calls the given [block] when this job completes, with the progress value 1.0.
@@ -138,7 +145,11 @@ interface JobInternal : Job, JobScope {
* There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick * There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick
* This object attempts to split that maximum amount of milliseconds equally between all jobs * This object attempts to split that maximum amount of milliseconds equally between all jobs
*/ */
class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJobtimeOptions) : JobDispatcher { class BukkitJobDispatcher(
private val plugin: PluginAware,
private val scope: CoroutineScope,
var options: TickJobtimeOptions
) : JobDispatcher {
// The currently registered bukkit scheduler task // The currently registered bukkit scheduler task
private var bukkitTask: BukkitTask? = null private var bukkitTask: BukkitTask? = null
// The jobs. // The jobs.
@@ -146,18 +157,18 @@ class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJo
override val jobs: List<Job> = _jobs override val jobs: List<Job> = _jobs
override fun dispatch(function: JobFunction): Job { override fun dispatch(function: JobFunction): Job {
val job: JobInternal = JobImpl(plugin, function) val job: JobInternal = JobImpl(scope, function)
if (bukkitTask == null) { if (bukkitTask == null) {
val completed = job.resume(options.jobTime.toLong()) val completed = job.resume(options.jobTime.toLong())
if (completed) return job if (completed) return job
bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() } bukkitTask = plugin.scheduleRepeating(options.tickInterval) { tickJobs() }
} }
_jobs.addFirst(job) _jobs.addFirst(job)
return job return job
} }
private fun tickCoroutineJobs() { private fun tickJobs() {
val jobs = _jobs val jobs = _jobs
if (jobs.isEmpty()) return if (jobs.isEmpty()) return
val tickStartTime = System.currentTimeMillis() val tickStartTime = System.currentTimeMillis()
@@ -237,7 +248,12 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
} }
} }
override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: JobUpdateLister): Job { override fun onProgressUpdate(
minDelay: Int,
minInterval: Int,
asCompletionListener: Boolean,
block: JobUpdateLister
): Job {
onProgressUpdate?.let { throw IllegalStateException() } onProgressUpdate?.let { throw IllegalStateException() }
if (asCompletionListener) onCompleted(block) if (asCompletionListener) onCompleted(block)
if (isComplete) return this if (isComplete) return this
@@ -296,7 +312,11 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
if (isStarted) { if (isStarted) {
continuation?.let { continuation?.let {
continuation = null continuation = null
it.resume(Unit)
wrapExternalCall {
it.resume(Unit)
}
return continuation == null return continuation == null
} }
return true return true
@@ -304,34 +324,45 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
isStarted = true isStarted = true
startTimeOrElapsedTime = System.currentTimeMillis() startTimeOrElapsedTime = System.currentTimeMillis()
coroutine.start()
wrapExternalCall {
coroutine.start()
}
return continuation == null return continuation == null
} }
private inline fun wrapExternalCall(block: () -> Unit) {
try {
block()
} catch (ex: Throwable) {
logger.error("Job $coroutine generated an exception", ex)
}
}
override suspend fun awaitCompletion() { override suspend fun awaitCompletion() {
coroutine.join() coroutine.join()
} }
private fun delegateProgress(curPortion: Double, portion: Double): JobScope = private fun delegateProgress(curPortion: Double, portion: Double): JobScope =
DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0)) DelegateScope(this, progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0))
override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion) override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion)
private inner class DelegateScope(val progressStart: Double, val portion: Double) : JobScope { private class DelegateScope(val parent: JobImpl, val progressStart: Double, val portion: Double) : JobScope {
override val elapsedTime: Long override val elapsedTime: Long
get() = this@JobImpl.elapsedTime get() = parent.elapsedTime
override suspend fun markSuspensionPoint() = override suspend fun markSuspensionPoint() =
this@JobImpl.markSuspensionPoint() parent.markSuspensionPoint()
override val progress: Double override val progress: Double
get() = (this@JobImpl.progress - progressStart) / portion get() = (parent.progress - progressStart) / portion
override fun setProgress(progress: Double) = override fun setProgress(progress: Double) =
this@JobImpl.setProgress(progressStart + progress * portion) parent.setProgress(progressStart + progress * portion)
override fun delegateProgress(portion: Double): JobScope = override fun delegateProgress(portion: Double): JobScope =
this@JobImpl.delegateProgress(this.portion, portion) parent.delegateProgress(this.portion, portion)
} }
} }

View File

@@ -56,7 +56,7 @@ interface ParcelBlockManager {
fun getRegion(parcel: ParcelId): Region fun getRegion(parcel: ParcelId): Region
fun getEntities(parcel: ParcelId): Collection<Entity> fun getEntities(region: Region): Collection<Entity>
fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean
@@ -92,8 +92,7 @@ inline fun ParcelBlockManager.tryDoBlockOperation(
abstract class ParcelBlockManagerBase : ParcelBlockManager { abstract class ParcelBlockManagerBase : ParcelBlockManager {
override fun getEntities(parcel: ParcelId): Collection<Entity> { override fun getEntities(region: Region): Collection<Entity> {
val region = getRegion(parcel)
val center = region.center val center = region.center
val centerLoc = Location(world, center.x, center.y, center.z) val centerLoc = Location(world, center.x, center.y, center.z)
val centerDist = (center - region.origin).add(0.2, 0.2, 0.2) val centerDist = (center - region.origin).add(0.2, 0.2, 0.2)

View File

@@ -83,7 +83,7 @@ interface ParcelContainer {
fun getParcelById(id: ParcelId): Parcel? fun getParcelById(id: ParcelId): Parcel?
fun nextEmptyParcel(): Parcel? suspend fun nextEmptyParcel(): Parcel?
} }

View File

@@ -13,9 +13,10 @@ import io.dico.parcels2.options.Options
import io.dico.parcels2.options.optionsMapper import io.dico.parcels2.options.optionsMapper
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.MainThreadDispatcher import io.dico.parcels2.util.MainThreadDispatcher
import io.dico.parcels2.util.PluginScheduler import io.dico.parcels2.util.PluginAware
import io.dico.parcels2.util.ext.tryCreate import io.dico.parcels2.util.ext.tryCreate
import io.dico.parcels2.util.isServerThread import io.dico.parcels2.util.isServerThread
import io.dico.parcels2.util.scheduleRepeating
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.generator.ChunkGenerator import org.bukkit.generator.ChunkGenerator
@@ -29,7 +30,7 @@ import kotlin.coroutines.CoroutineContext
val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin") val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
private inline val plogger get() = logger private inline val plogger get() = logger
class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler { class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginAware {
lateinit var optionsFile: File; private set lateinit var optionsFile: File; private set
lateinit var options: Options; private set lateinit var options: Options; private set
lateinit var parcelProvider: ParcelProvider; private set lateinit var parcelProvider: ParcelProvider; private set
@@ -43,7 +44,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
override val coroutineContext: CoroutineContext = MainThreadDispatcher(this) override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
override val plugin: Plugin get() = this override val plugin: Plugin get() = this
val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, options.tickJobtime) } val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, this, options.tickJobtime) }
override fun onEnable() { override fun onEnable() {
plogger.info("Is server thread: ${isServerThread()}") plogger.info("Is server thread: ${isServerThread()}")
@@ -147,7 +148,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
} }
} }
scheduleRepeating(100, 5, entityTracker::tick) scheduleRepeating(5, delay = 100, task = entityTracker::tick)
} }
} }

View File

@@ -3,11 +3,13 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.checkPlayerNameValid
import io.dico.parcels2.util.ext.PLAYER_NAME_PLACEHOLDER import io.dico.parcels2.util.ext.PLAYER_NAME_PLACEHOLDER
import io.dico.parcels2.util.ext.isValid import io.dico.parcels2.util.ext.isValid
import io.dico.parcels2.util.ext.uuid import io.dico.parcels2.util.ext.uuid
import io.dico.parcels2.util.getOfflinePlayer import io.dico.parcels2.util.getOfflinePlayer
import io.dico.parcels2.util.getPlayerName import io.dico.parcels2.util.getPlayerName
import io.dico.parcels2.util.isPlayerNameValid
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import java.util.UUID import java.util.UUID
@@ -32,7 +34,7 @@ interface PlayerProfile {
companion object { companion object {
fun safe(uuid: UUID?, name: String?): PlayerProfile? { fun safe(uuid: UUID?, name: String?): PlayerProfile? {
if (uuid != null) return Real(uuid, name) if (uuid != null) return Real(uuid, if (name != null && !isPlayerNameValid(name)) null else name)
if (name != null) return invoke(name) if (name != null) return invoke(name)
return null return null
} }
@@ -47,7 +49,7 @@ interface PlayerProfile {
} }
operator fun invoke(name: String): PlayerProfile { operator fun invoke(name: String): PlayerProfile {
if (name == Star.name) return Star if (name equalsIgnoreCase Star.name) return Star
return Fake(name) return Fake(name)
} }
@@ -60,12 +62,17 @@ interface PlayerProfile {
return RealImpl(player.uuid, null) return RealImpl(player.uuid, null)
} }
fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile { fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile? {
if (!allowReal) { if (!allowReal) {
if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true") if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
return Fake(input) return Fake(input)
} }
if (!isPlayerNameValid(input)) {
if (!allowFake) return null
return Fake(input)
}
if (input == Star.name) return Star if (input == Star.name) return Star
return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input) return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input)
@@ -92,19 +99,19 @@ interface PlayerProfile {
companion object { companion object {
fun byName(name: String): PlayerProfile { fun byName(name: String): PlayerProfile {
if (name == Star.name) return Star if (name equalsIgnoreCase Star.name) return Star
return Unresolved(name) return Unresolved(name)
} }
operator fun invoke(uuid: UUID, name: String?): Real { operator fun invoke(uuid: UUID, name: String?): Real {
if (name == Star.name || uuid == Star.uuid) return Star if (name equalsIgnoreCase Star.name || uuid == Star.uuid) return Star
return RealImpl(uuid, name) return RealImpl(uuid, name)
} }
fun safe(uuid: UUID?, name: String?): Real? { fun safe(uuid: UUID?, name: String?): Real? {
if (name == Star.name || uuid == Star.uuid) return Star if (name equalsIgnoreCase Star.name || uuid == Star.uuid) return Star
if (uuid == null) return null if (uuid == null) return null
return RealImpl(uuid, name) return RealImpl(uuid, if (name != null && !isPlayerNameValid(name)) null else name)
} }
} }
@@ -130,7 +137,7 @@ interface PlayerProfile {
override val nameOrBukkitName: String get() = name override val nameOrBukkitName: String get() = name
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean { override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
return allowNameMatch && player.name == name return allowNameMatch && player.name equalsIgnoreCase name
} }
override fun toString() = "${javaClass.simpleName}($name)" override fun toString() = "${javaClass.simpleName}($name)"
@@ -138,13 +145,17 @@ interface PlayerProfile {
class Fake(name: String) : NameOnly(name) { class Fake(name: String) : NameOnly(name) {
override fun equals(other: PlayerProfile): Boolean { override fun equals(other: PlayerProfile): Boolean {
return other is Fake && other.name == name return other is Fake && other.name equalsIgnoreCase name
} }
} }
class Unresolved(name: String) : NameOnly(name) { class Unresolved(name: String) : NameOnly(name) {
init {
checkPlayerNameValid(name)
}
override fun equals(other: PlayerProfile): Boolean { override fun equals(other: PlayerProfile): Boolean {
return other is Unresolved && name == other.name return other is Unresolved && name equalsIgnoreCase other.name
} }
suspend fun tryResolveSuspendedly(storage: Storage): Real? { suspend fun tryResolveSuspendedly(storage: Storage): Real? {
@@ -171,11 +182,24 @@ interface PlayerProfile {
} }
private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real { private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real {
init {
name?.let { checkPlayerNameValid(it) }
}
override fun toString() = "Real($notNullName)" override fun toString() = "Real($notNullName)"
} }
} }
private infix fun String?.equalsIgnoreCase(other: String): Boolean {
if (this == null) return false
if (length != other.length) return false
repeat(length) { i ->
if (this[i].toLowerCase() != other[i].toLowerCase()) return false
}
return true
}
suspend fun PlayerProfile.resolved(storage: Storage, resolveToFake: Boolean = false): PlayerProfile? = suspend fun PlayerProfile.resolved(storage: Storage, resolveToFake: Boolean = false): PlayerProfile? =
when (this) { when (this) {
is PlayerProfile.Unresolved -> tryResolveSuspendedly(storage) is PlayerProfile.Unresolved -> tryResolveSuspendedly(storage)

View File

@@ -0,0 +1,38 @@
package io.dico.parcels2.blockvisitor
import io.dico.parcels2.util.math.Vec3d
import org.bukkit.Location
import org.bukkit.World
import org.bukkit.entity.Entity
import org.bukkit.entity.Minecart
/*
open class EntityCopy<T : Entity>(entity: T) {
val type = entity.type
@Suppress("UNCHECKED_CAST")
fun spawn(world: World, position: Vec3d): T {
val entity = world.spawnEntity(Location(null, position.x, position.y, position.z), type) as T
setAttributes(entity)
return entity
}
open fun setAttributes(entity: T) {}
}
open class MinecartCopy<T : Minecart>(entity: T) : EntityCopy<T>(entity) {
val damage = entity.damage
val maxSpeed = entity.maxSpeed
val isSlowWhenEmpty = entity.isSlowWhenEmpty
val flyingVelocityMod = entity.flyingVelocityMod
val derailedVelocityMod = entity.derailedVelocityMod
val displayBlockData = entity.displayBlockData
val displayBlockOffset = entity.displayBlockOffset
override fun setAttributes(entity: T) {
super.setAttributes(entity)
entity.damage = damage
entity.displayBlockData = displayBlockData
entity.displayBlockOffset = displayBlockOffset
}
}*/

View File

@@ -6,7 +6,6 @@ import io.dico.dicore.command.parameter.Parameter
import io.dico.dicore.command.parameter.type.ParameterConfig import io.dico.dicore.command.parameter.type.ParameterConfig
import io.dico.dicore.command.parameter.type.ParameterType import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.command.ProfileKind.Companion.ANY
import io.dico.parcels2.command.ProfileKind.Companion.FAKE import io.dico.parcels2.command.ProfileKind.Companion.FAKE
import io.dico.parcels2.command.ProfileKind.Companion.REAL import io.dico.parcels2.command.ProfileKind.Companion.REAL
import org.bukkit.Location import org.bukkit.Location
@@ -53,6 +52,7 @@ annotation class ProfileKind(val kind: Int) {
const val REAL = 1 const val REAL = 1
const val FAKE = 2 const val FAKE = 2
const val ANY = REAL or FAKE const val ANY = REAL or FAKE
const val ALLOW_INVALID = 4
override fun toParameterInfo(annotation: ProfileKind): Int { override fun toParameterInfo(annotation: ProfileKind): Int {
return annotation.kind return annotation.kind
@@ -62,13 +62,20 @@ annotation class ProfileKind(val kind: Int) {
class ProfileParameterType : ParameterType<PlayerProfile, Int>(PlayerProfile::class.java, ProfileKind) { class ProfileParameterType : ParameterType<PlayerProfile, Int>(PlayerProfile::class.java, ProfileKind) {
override fun parse(parameter: Parameter<PlayerProfile, Int>, sender: CommandSender, buffer: ArgumentBuffer): PlayerProfile { override fun parse(parameter: Parameter<PlayerProfile, Int>, sender: CommandSender, buffer: ArgumentBuffer): PlayerProfile? {
val info = parameter.paramInfo ?: REAL val info = parameter.paramInfo ?: REAL
val allowReal = (info and REAL) != 0 val allowReal = (info and REAL) != 0
val allowFake = (info and FAKE) != 0 val allowFake = (info and FAKE) != 0
val input = buffer.next()!! val input = buffer.next()!!
return PlayerProfile.byName(input, allowReal, allowFake)
val profile = PlayerProfile.byName(input, allowReal, allowFake)
if (profile == null && (info and ProfileKind.ALLOW_INVALID) == 0) {
invalidInput(parameter, "\'$input\' is not a valid player name")
}
return profile
} }
override fun complete( override fun complete(

View File

@@ -25,7 +25,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
abstract suspend fun getParcelSuspend(storage: Storage): Parcel? abstract suspend fun getParcelSuspend(storage: Storage): Parcel?
class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) : ParcelTarget(world, parsedKind, isDefault) { class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) :
ParcelTarget(world, parsedKind, isDefault) {
override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel() override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel()
fun getParcel() = id?.let { world.getParcelById(it) } fun getParcel() = id?.let { world.getParcelById(it) }
val isPath: Boolean get() = id == null val isPath: Boolean get() = id == null
@@ -78,7 +79,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
const val DEFAULT_KIND = REAL const val DEFAULT_KIND = REAL
const val PREFER_OWNED_FOR_DEFAULT = 8 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default const val PREFER_OWNED_FOR_DEFAULT =
8 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default
// instead of parcel that the player is in // instead of parcel that the player is in
override fun toParameterInfo(annotation: TargetKind): Int { override fun toParameterInfo(annotation: TargetKind): Int {
@@ -90,7 +92,11 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) : class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) :
ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) { ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) {
override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget { override fun parse(
parameter: Parameter<ParcelTarget, Int>,
sender: CommandSender,
buffer: ArgumentBuffer
): ParcelTarget {
var input = buffer.next()!! var input = buffer.next()!!
val worldString = input.substringBefore("/", missingDelimiterValue = "") val worldString = input.substringBefore("/", missingDelimiterValue = "")
input = input.substringAfter("/") input = input.substringAfter("/")
@@ -105,13 +111,20 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
val kind = parameter.paramInfo ?: DEFAULT_KIND val kind = parameter.paramInfo ?: DEFAULT_KIND
if (input.contains(',')) { if (input.contains(',')) {
if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index") if (kind and ID == 0) invalidInput(parameter,
"You must specify a parcel by OWNER, that is, an owner and index")
return ByID(world, getId(parameter, input), kind, false) return ByID(world, getId(parameter, input), kind, false)
} }
if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma") if (kind and OWNER == 0) invalidInput(parameter,
"You must specify a parcel by ID, that is, the x and z component separated by a comma")
val (owner, index) = getHomeIndex(parameter, kind, sender, input) val (owner, index) = getHomeIndex(parameter, kind, sender, input)
return ByOwner(world, owner, index, kind, false, onResolveFailure = { invalidInput(parameter, "The player $input does not exist") }) return ByOwner(world,
owner,
index,
kind,
false,
onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
} }
private fun getId(parameter: Parameter<*, *>, input: String): Vec2i { private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
@@ -124,7 +137,12 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
return Vec2i(x, z) return Vec2i(x, z)
} }
private fun getHomeIndex(parameter: Parameter<*, *>, kind: Int, sender: CommandSender, input: String): Pair<PlayerProfile, Int> { private fun getHomeIndex(
parameter: Parameter<*, *>,
kind: Int,
sender: CommandSender,
input: String
): Pair<PlayerProfile, Int> {
val splitIdx = input.indexOf(':') val splitIdx = input.indexOf(':')
val ownerString: String val ownerString: String
val index: Int? val index: Int?
@@ -155,10 +173,11 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer") ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
} }
val owner = if (ownerString.isEmpty()) val owner = (if (ownerString.isEmpty())
PlayerProfile(requirePlayer(sender, parameter, "the player")) PlayerProfile(requirePlayer(sender, parameter, "the player"))
else else
PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0) PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0))
?: invalidInput(parameter, "\'$ownerString\' is not a valid player name")
return owner to (index ?: 0) return owner to (index ?: 0)
} }
@@ -168,7 +187,11 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
return sender return sender
} }
override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget? { override fun getDefaultValue(
parameter: Parameter<ParcelTarget, Int>,
sender: CommandSender,
buffer: ArgumentBuffer
): ParcelTarget? {
val kind = parameter.paramInfo ?: DEFAULT_KIND val kind = parameter.paramInfo ?: DEFAULT_KIND
val useLocation = when { val useLocation = when {
kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0 kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0
@@ -178,7 +201,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
} }
val player = requirePlayer(sender, parameter, "the parcel") val player = requirePlayer(sender, parameter, "the parcel")
val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter, "You must be in a parcel world to omit the parcel") val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter,
"You must be in a parcel world to omit the parcel")
if (useLocation) { if (useLocation) {
val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos } val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
return ByID(world, id, kind, true) return ByID(world, id, kind, true)

View File

@@ -11,6 +11,7 @@ import org.bukkit.block.BlockFace
import org.bukkit.block.Skull import org.bukkit.block.Skull
import org.bukkit.block.data.type.Slab import org.bukkit.block.data.type.Slab
import org.bukkit.block.data.type.WallSign import org.bukkit.block.data.type.WallSign
import org.bukkit.entity.Player
import java.util.Random import java.util.Random
private val airType = Bukkit.createBlockData(Material.AIR) private val airType = Bukkit.createBlockData(Material.AIR)
@@ -285,17 +286,29 @@ class DefaultParcelGenerator(
val floorType = o.floorType val floorType = o.floorType
val fillType = o.fillType val fillType = o.fillType
for ((index, vec) in blocks.withIndex()) { delegateWork(0.95) {
markSuspensionPoint() for ((index, vec) in blocks.withIndex()) {
val y = vec.y markSuspensionPoint()
val blockType = when { val y = vec.y
y > floorHeight -> airType val blockType = when {
y == floorHeight -> floorType y > floorHeight -> airType
else -> fillType y == floorHeight -> floorType
else -> fillType
}
world[vec].blockData = blockType
setProgress((index + 1) / blockCount)
} }
world[vec].blockData = blockType
setProgress((index + 1) / blockCount)
} }
delegateWork {
val entities = getEntities(region)
for ((index, entity) in entities.withIndex()) {
if (entity is Player) continue
entity.remove()
setProgress((index + 1) / entities.size.toDouble())
}
}
} }
override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> { override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {

View File

@@ -2,12 +2,18 @@ package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.Schematic import io.dico.parcels2.blockvisitor.Schematic
import io.dico.parcels2.util.math.Region
import io.dico.parcels2.util.math.Vec3d
import io.dico.parcels2.util.math.Vec3i
import io.dico.parcels2.util.schedule import io.dico.parcels2.util.schedule
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.World
import org.bukkit.WorldCreator import org.bukkit.WorldCreator
import org.bukkit.entity.Entity
import org.bukkit.util.Vector
import org.joda.time.DateTime import org.joda.time.DateTime
class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
@@ -45,7 +51,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
private fun loadWorlds0() { private fun loadWorlds0() {
if (Bukkit.getWorlds().isEmpty()) { if (Bukkit.getWorlds().isEmpty()) {
plugin.schedule(::loadWorlds0) plugin.schedule { loadWorlds0() }
plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet") plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet")
return return
} }
@@ -64,7 +70,8 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
WorldCreator(worldName).generator(generator).createWorld() WorldCreator(worldName).generator(generator).createWorld()
} }
parcelWorld = ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime,::DefaultParcelContainer) parcelWorld =
ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime, ::DefaultParcelContainer)
if (!worldExists) { if (!worldExists) {
val time = DateTime.now() val time = DateTime.now()
@@ -73,7 +80,8 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
newlyCreatedWorlds.add(parcelWorld) newlyCreatedWorlds.add(parcelWorld)
} else { } else {
GlobalScope.launch(context = Dispatchers.Unconfined) { GlobalScope.launch(context = Dispatchers.Unconfined) {
parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now() parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?:
DateTime.now()
} }
} }
@@ -84,7 +92,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
} }
private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) { private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) {
plugin.launch(Dispatchers.Default) { plugin.launch {
val migration = plugin.options.migration val migration = plugin.options.migration
if (migration.enabled) { if (migration.enabled) {
migration.instance?.newInstance()?.apply { migration.instance?.newInstance()?.apply {
@@ -155,10 +163,32 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
} }
override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? { override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? {
val blockManager1 = getWorldById(parcelId1.worldId)?.blockManager ?: return null val world1 = getWorldById(parcelId1.worldId) ?: return null
val blockManager2 = getWorldById(parcelId2.worldId)?.blockManager ?: return null val world2 = getWorldById(parcelId2.worldId) ?: return null
val blockManager1 = world1.blockManager
val blockManager2 = world2.blockManager
class CopyTarget(val world: World, val region: Region)
class CopySource(val origin: Vec3i, val schematic: Schematic, val entities: Collection<Entity>)
suspend fun JobScope.copy(source: CopySource, target: CopyTarget) {
with(source.schematic) { paste(target.world, target.region.origin) }
for (entity in source.entities) {
entity.velocity = Vector(0, 0, 0)
val location = entity.location
location.world = target.world
val coords = target.region.origin + (Vec3d(entity.location) - source.origin)
coords.copyInto(location)
entity.teleport(location)
}
}
return trySubmitBlockVisitor(Permit(), parcelId1, parcelId2) { return trySubmitBlockVisitor(Permit(), parcelId1, parcelId2) {
val temporaryParcel = world1.nextEmptyParcel()
?: world2.nextEmptyParcel()
?: return@trySubmitBlockVisitor
var region1 = blockManager1.getRegion(parcelId1) var region1 = blockManager1.getRegion(parcelId1)
var region2 = blockManager2.getRegion(parcelId2) var region2 = blockManager2.getRegion(parcelId2)
@@ -168,10 +198,41 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
region2 = region2.withSize(size) region2 = region2.withSize(size)
} }
val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(blockManager1.world, region1) } } // Teleporting entities safely requires a different approach:
val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(blockManager2.world, region2) } } // * Copy schematic1 into temporary location
delegateWork(0.25) { with(schematicOf1) { paste(blockManager2.world, region2.origin) } } // * Teleport entities1 into temporary location
delegateWork(0.25) { with(schematicOf2) { paste(blockManager1.world, region1.origin) } } // * Copy schematic2 into parcel1
// * Teleport entities2 into parcel1
// * Copy schematic1 into parcel2
// * Teleport entities1 into parcel2
// * Clear temporary location
lateinit var source1: CopySource
lateinit var source2: CopySource
delegateWork(0.30) {
val schematicOf1 = delegateWork(0.50) { Schematic().apply { load(blockManager1.world, region1) } }
val schematicOf2 = delegateWork(0.50) { Schematic().apply { load(blockManager2.world, region2) } }
source1 = CopySource(region1.origin, schematicOf1, blockManager1.getEntities(region1))
source2 = CopySource(region2.origin, schematicOf2, blockManager2.getEntities(region2))
}
val target1 = CopyTarget(blockManager1.world, region1)
val target2 = CopyTarget(blockManager2.world, region2)
val targetTemp = CopyTarget(
temporaryParcel.world.world,
temporaryParcel.world.blockManager.getRegion(temporaryParcel.id)
)
delegateWork {
delegateWork(1.0 / 3.0) { copy(source1, targetTemp) }
delegateWork(1.0 / 3.0) { copy(source2, target1) }
delegateWork(1.0 / 3.0) { copy(source1, target2) }
}
// Separate job. Whatever
temporaryParcel.world.blockManager.clearParcel(temporaryParcel.id)
} }
} }

View File

@@ -72,9 +72,11 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
override fun init() { override fun init() {
synchronized { synchronized {
if (isShutdown || isConnected) throw IllegalStateException() if (isShutdown || isConnected) throw IllegalStateException()
dataSource = dataSourceFactory() val dataSource = dataSourceFactory()
database = Database.connect(dataSource!!) this.dataSource = dataSource
transaction(database!!) { val database = Database.connect(dataSource)
this.database = database
transaction(database) {
create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT) create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT)
} }
} }
@@ -84,7 +86,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
synchronized { synchronized {
if (isShutdown) throw IllegalStateException() if (isShutdown) throw IllegalStateException()
isShutdown = true isShutdown = true
coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown")) coroutineContext.cancel(CancellationException("ExposedBacking shutdown"))
dataSource?.let { dataSource?.let {
(it as? HikariDataSource)?.close() (it as? HikariDataSource)?.close()
} }

View File

@@ -3,6 +3,7 @@ package io.dico.parcels2.util
import io.dico.parcels2.util.ext.isValid import io.dico.parcels2.util.ext.isValid
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import java.lang.IllegalArgumentException
import java.util.UUID import java.util.UUID
fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name
@@ -12,3 +13,11 @@ fun getOfflinePlayer(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid)
fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid } fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
fun isServerThread(): Boolean = Thread.currentThread().name == "Server thread" fun isServerThread(): Boolean = Thread.currentThread().name == "Server thread"
fun isPlayerNameValid(name: String): Boolean =
name.length in 3..16
&& name.find { it !in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" } == null
fun checkPlayerNameValid(name: String) {
if (!isPlayerNameValid(name)) throw IllegalArgumentException("Invalid player name: $name")
}

View File

@@ -0,0 +1,18 @@
package io.dico.parcels2.util
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
interface PluginAware {
val plugin: Plugin
}
inline fun PluginAware.schedule(delay: Int = 0, crossinline task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskLater(plugin, { task() }, delay.toLong())
}
inline fun PluginAware.scheduleRepeating(interval: Int, delay: Int = 0, crossinline task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskTimer(plugin, { task() }, delay.toLong(), interval.toLong())
}

View File

@@ -1,20 +0,0 @@
package io.dico.parcels2.util
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
interface PluginScheduler {
val plugin: Plugin
fun schedule(delay: Int, task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskLater(plugin, task, delay.toLong())
}
fun scheduleRepeating(delay: Int, interval: Int, task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskTimer(plugin, task, delay.toLong(), interval.toLong())
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun PluginScheduler.schedule(noinline task: () -> Unit) = schedule(0, task)

View File

@@ -11,6 +11,8 @@ data class Vec3d(
constructor(loc: Location) : this(loc.x, loc.y, loc.z) constructor(loc: Location) : this(loc.x, loc.y, loc.z)
operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z) operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z)
operator fun plus(o: Vec3i) = Vec3d(x + o.x, y + o.y, z + o.z)
operator fun minus(o: Vec3d) = Vec3d(x - o.x, y - o.y, z - o.z)
operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z) operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z)
infix fun addX(o: Double) = Vec3d(x + o, y, z) infix fun addX(o: Double) = Vec3d(x + o, y, z)
infix fun addY(o: Double) = Vec3d(x, y + o, z) infix fun addY(o: Double) = Vec3d(x, y + o, z)
@@ -50,4 +52,10 @@ data class Vec3d(
Dimension.Y -> addY(value) Dimension.Y -> addY(value)
Dimension.Z -> addZ(value) Dimension.Z -> addZ(value)
} }
fun copyInto(loc: Location) {
loc.x = x
loc.y = y
loc.z = z
}
} }

View File

@@ -15,7 +15,9 @@ data class Vec3i(
fun toVec2i() = Vec2i(x, z) fun toVec2i() = Vec2i(x, z)
operator fun plus(o: Vec3i) = Vec3i(x + o.x, y + o.y, z + o.z) operator fun plus(o: Vec3i) = Vec3i(x + o.x, y + o.y, z + o.z)
operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z)
operator fun minus(o: Vec3i) = Vec3i(x - o.x, y - o.y, z - o.z) operator fun minus(o: Vec3i) = Vec3i(x - o.x, y - o.y, z - o.z)
operator fun minus(o: Vec3d) = Vec3d(x - o.x, y - o.y, z - o.z)
infix fun addX(o: Int) = Vec3i(x + o, y, z) infix fun addX(o: Int) = Vec3i(x + o, y, z)
infix fun addY(o: Int) = Vec3i(x, y + o, z) infix fun addY(o: Int) = Vec3i(x, y + o, z)
infix fun addZ(o: Int) = Vec3i(x, y, z + o) infix fun addZ(o: Int) = Vec3i(x, y, z + o)

View File

@@ -0,0 +1,9 @@
package io.dico.parcels2.util
fun doParallel() {
val array = IntArray(1000)
IntRange(0, 1000).chunked()
}

View File

@@ -71,8 +71,8 @@ Database
- -
Find and patch ways to add new useless entries (for regular players at least) Find and patch ways to add new useless entries (for regular players at least)
Prevent invalid player names from being saved to the database. ~~Prevent invalid player names from being saved to the database.
Here, invalid player names mean names that contain invalid characters. Here, invalid player names mean names that contain invalid characters.~~
Use an atomic GET OR INSERT query so that parallel execution doesn't cause problems Use an atomic GET OR INSERT query so that parallel execution doesn't cause problems
(as is currently the case when migrating). (as is currently the case when migrating).
@@ -87,7 +87,7 @@ Implement a container that doesn't require loading all parcel data on startup (C
After testing on Redstoner After testing on Redstoner
- -
Clear (and swap) entities on /p clear etc ~~Clear (and swap) entities on /p clear etc~~
Fix command lag Fix command lag
Chorus fruit can grow outside plots Chorus fruit can grow outside plots
Vines can grow outside plots Vines can grow outside plots