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 {
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"
}
@@ -30,10 +30,11 @@ allprojects {
maven("https://dl.bintray.com/kotlin/kotlin-dev/")
maven("https://dl.bintray.com/kotlin/kotlin-eap/")
maven("https://dl.bintray.com/kotlin/kotlinx/")
maven("http://maven.sk89q.com/repo")
}
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.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") {
apply<KotlinPlatformJvmPlugin>()
dependencies {
c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect"))
c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13"))
c.kotlinStd(coroutinesCore)
compile(project(":dicore3:dicore3-core"))
compile("com.thoughtworks.paranamer:paranamer:2.8")
@@ -72,12 +75,13 @@ dependencies {
c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect"))
c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13"))
c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.7-eap13")
c.kotlinStd(coroutinesCore)
c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.12")
// not on sk89q maven repo yet
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/plugins/worldedit-bukkit-7.0.0-beta-01.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("joda-time:joda-time:2.10")
@@ -167,7 +171,6 @@ val ConfigurationContainer.`kotlinStd`: Configuration
get() = findByName("kotlinStd") ?: create("kotlinStd").let { compileClasspath.extendsFrom(it) }
fun Jar.fromFiles(files: Iterable<File>) {
return
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
public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) {
ExecutionContext context = new ExecutionContext(sender, buffer, true);
long start = System.currentTimeMillis();
try {
ICommandAddress target = getCommandTarget(context, buffer);
@@ -269,6 +270,11 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
} catch (CommandException ex) {
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;
assignDefaultValuesToUncomputedParams();
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.GenerateCommands;
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 java.lang.reflect.InvocationTargetException;
@@ -11,14 +13,11 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public final class ReflectiveCommand extends Command {
private static final int continuationMask = 1 << 3;
private final Cmd cmdAnnotation;
private final Method method;
private final Object instance;
private String[] parameterOrder;
// hasContinuation | hasContext | hasSender | hasReceiver
private final int flags;
private final int callFlags;
ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
if (!method.isAnnotationPresent(Cmd.class)) {
@@ -48,7 +47,7 @@ public final class ReflectiveCommand extends Command {
this.method = method;
this.instance = instance;
this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
this.callFlags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
}
public Method getMethod() {
@@ -59,12 +58,22 @@ public final class ReflectiveCommand extends Command {
return instance;
}
public String getCmdName() { return cmdAnnotation.value(); }
public String getCmdName() {
return cmdAnnotation.value();
}
public int getCallFlags() {
return callFlags;
}
void setParameterOrder(String[] parameterOrder) {
this.parameterOrder = parameterOrder;
}
public int getParameterNum() {
return parameterOrder.length;
}
ICommandAddress getAddress() {
ChildCommandAddress result = new ChildCommandAddress();
result.setCommand(this);
@@ -86,54 +95,24 @@ public final class ReflectiveCommand extends Command {
@Override
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];
int i = 0;
int mask = 1;
if ((flags & mask) != 0) {
// Has receiver
CheckedSupplier<Object, CommandException> receiverFunction = () -> {
try {
args[i++] = ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName());
return ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName());
} catch (Exception ex) {
handleException(ex);
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;
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);
return callSynchronously(callArgs);
}
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);
}
private String callAsCoroutine(ExecutionContext context, Object[] args) {
return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandInterceptor) instance, context, args);
private String callAsCoroutine(ExecutionContext executionContext, Object[] args) throws CommandException {
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.PatternSyntaxException;
import static io.dico.dicore.command.registration.reflect.ReflectiveCallFlags.*;
/**
* 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();
}
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();
boolean hasReceiverParameter = false;
boolean hasSenderParameter = false;
boolean hasContextParameter = false;
boolean hasContinuationParameter = false;
int start = 0;
int end = parameters.length;
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
&& ICommandReceiver.class.isAssignableFrom(parameters[start].getType())) {
hasReceiverParameter = true;
start++;
&& ICommandReceiver.class.isAssignableFrom(callParameters[start].getType())) {
flags |= RECEIVER_BIT;
++start;
}
if (parameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = parameters[start].getType())) {
hasSenderParameter = true;
start++;
if (callParameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = callParameters[start].getType())) {
flags |= SENDER_BIT;
++start;
}
if (parameters.length > start && parameters[start].getType() == ExecutionContext.class) {
hasContextParameter = true;
start++;
if (callParameters.length > start && callParameters[start].getType() == ExecutionContext.class) {
flags |= CONTEXT_BIT;
++start;
}
if (parameters.length > start && parameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
hasContinuationParameter = true;
end--;
if (callParameters.length > start && callParameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
flags |= CONTINUATION_BIT;
--end;
}
String[] parameterNames = lookupParameterNames(method, parameters, start);
String[] parameterNames = lookupParameterNames(method, callParameters, start);
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);
}
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);
if (cmdPermissions != null) {
@@ -277,6 +275,7 @@ public class ReflectiveRegistration {
command.setDescription();
}
boolean hasSenderParameter = hasCallArg(flags, SENDER_BIT);
if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
command.addContextFilter(IContextFilter.PLAYER_ONLY);
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
@@ -287,17 +286,9 @@ public class ReflectiveRegistration {
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);
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;
}

View File

@@ -17,20 +17,23 @@ fun isSuspendFunction(method: Method): Boolean {
return func.isSuspend
}
fun callAsCoroutine(
command: ReflectiveCommand,
factory: ICommandInterceptor,
context: ExecutionContext,
@Throws(CommandException::class)
fun callCommandAsCoroutine(
executionContext: ExecutionContext,
coroutineContext: CoroutineContext,
continuationIndex: Int,
method: Method,
instance: Any?,
args: Array<Any?>
): 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,
// 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.
val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) {
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 {
val chatHandler = context.address.chatHandler
val chatHandler = executionContext.address.chatHandler
try {
val result = job.getResult()
chatHandler.sendMessage(context.sender, EMessageType.RESULT, result)
chatHandler.sendMessage(executionContext.sender, EMessageType.RESULT, result)
} 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
import io.dico.parcels2.util.PluginAware
import io.dico.parcels2.util.math.clampMin
import io.dico.parcels2.util.scheduleRepeating
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart.LAZY
@@ -74,7 +76,12 @@ interface Job : JobAndScopeMembersUnion {
*
* 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.
@@ -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
* 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
private var bukkitTask: BukkitTask? = null
// The jobs.
@@ -146,18 +157,18 @@ class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJo
override val jobs: List<Job> = _jobs
override fun dispatch(function: JobFunction): Job {
val job: JobInternal = JobImpl(plugin, function)
val job: JobInternal = JobImpl(scope, function)
if (bukkitTask == null) {
val completed = job.resume(options.jobTime.toLong())
if (completed) return job
bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() }
bukkitTask = plugin.scheduleRepeating(options.tickInterval) { tickJobs() }
}
_jobs.addFirst(job)
return job
}
private fun tickCoroutineJobs() {
private fun tickJobs() {
val jobs = _jobs
if (jobs.isEmpty()) return
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() }
if (asCompletionListener) onCompleted(block)
if (isComplete) return this
@@ -296,7 +312,11 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
if (isStarted) {
continuation?.let {
continuation = null
wrapExternalCall {
it.resume(Unit)
}
return continuation == null
}
return true
@@ -304,34 +324,45 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
isStarted = true
startTimeOrElapsedTime = System.currentTimeMillis()
wrapExternalCall {
coroutine.start()
}
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() {
coroutine.join()
}
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)
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
get() = this@JobImpl.elapsedTime
get() = parent.elapsedTime
override suspend fun markSuspensionPoint() =
this@JobImpl.markSuspensionPoint()
parent.markSuspensionPoint()
override val progress: Double
get() = (this@JobImpl.progress - progressStart) / portion
get() = (parent.progress - progressStart) / portion
override fun setProgress(progress: Double) =
this@JobImpl.setProgress(progressStart + progress * portion)
parent.setProgress(progressStart + progress * portion)
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 getEntities(parcel: ParcelId): Collection<Entity>
fun getEntities(region: Region): Collection<Entity>
fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean
@@ -92,8 +92,7 @@ inline fun ParcelBlockManager.tryDoBlockOperation(
abstract class ParcelBlockManagerBase : ParcelBlockManager {
override fun getEntities(parcel: ParcelId): Collection<Entity> {
val region = getRegion(parcel)
override fun getEntities(region: Region): Collection<Entity> {
val center = region.center
val centerLoc = Location(world, center.x, center.y, center.z)
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 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.storage.Storage
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.isServerThread
import io.dico.parcels2.util.scheduleRepeating
import kotlinx.coroutines.CoroutineScope
import org.bukkit.Bukkit
import org.bukkit.generator.ChunkGenerator
@@ -29,7 +30,7 @@ import kotlin.coroutines.CoroutineContext
val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
private inline val plogger get() = logger
class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginAware {
lateinit var optionsFile: File; private set
lateinit var options: Options; 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 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() {
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
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.isValid
import io.dico.parcels2.util.ext.uuid
import io.dico.parcels2.util.getOfflinePlayer
import io.dico.parcels2.util.getPlayerName
import io.dico.parcels2.util.isPlayerNameValid
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import java.util.UUID
@@ -32,7 +34,7 @@ interface PlayerProfile {
companion object {
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)
return null
}
@@ -47,7 +49,7 @@ interface PlayerProfile {
}
operator fun invoke(name: String): PlayerProfile {
if (name == Star.name) return Star
if (name equalsIgnoreCase Star.name) return Star
return Fake(name)
}
@@ -60,12 +62,17 @@ interface PlayerProfile {
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 (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
return Fake(input)
}
if (!isPlayerNameValid(input)) {
if (!allowFake) return null
return Fake(input)
}
if (input == Star.name) return Star
return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input)
@@ -92,19 +99,19 @@ interface PlayerProfile {
companion object {
fun byName(name: String): PlayerProfile {
if (name == Star.name) return Star
if (name equalsIgnoreCase Star.name) return Star
return Unresolved(name)
}
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)
}
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
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 fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
return allowNameMatch && player.name == name
return allowNameMatch && player.name equalsIgnoreCase name
}
override fun toString() = "${javaClass.simpleName}($name)"
@@ -138,13 +145,17 @@ interface PlayerProfile {
class Fake(name: String) : NameOnly(name) {
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) {
init {
checkPlayerNameValid(name)
}
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? {
@@ -171,11 +182,24 @@ interface PlayerProfile {
}
private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real {
init {
name?.let { checkPlayerNameValid(it) }
}
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? =
when (this) {
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.ParameterType
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.REAL
import org.bukkit.Location
@@ -53,6 +52,7 @@ annotation class ProfileKind(val kind: Int) {
const val REAL = 1
const val FAKE = 2
const val ANY = REAL or FAKE
const val ALLOW_INVALID = 4
override fun toParameterInfo(annotation: ProfileKind): Int {
return annotation.kind
@@ -62,13 +62,20 @@ annotation class ProfileKind(val kind: Int) {
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 allowReal = (info and REAL) != 0
val allowFake = (info and FAKE) != 0
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(

View File

@@ -25,7 +25,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
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()
fun getParcel() = id?.let { world.getParcelById(it) }
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 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
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) :
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()!!
val worldString = input.substringBefore("/", missingDelimiterValue = "")
input = input.substringAfter("/")
@@ -105,13 +111,20 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
val kind = parameter.paramInfo ?: DEFAULT_KIND
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)
}
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)
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 {
@@ -124,7 +137,12 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
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 ownerString: String
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")
}
val owner = if (ownerString.isEmpty())
val owner = (if (ownerString.isEmpty())
PlayerProfile(requirePlayer(sender, parameter, "the player"))
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)
}
@@ -168,7 +187,11 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
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 useLocation = when {
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 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) {
val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
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.data.type.Slab
import org.bukkit.block.data.type.WallSign
import org.bukkit.entity.Player
import java.util.Random
private val airType = Bukkit.createBlockData(Material.AIR)
@@ -285,6 +286,7 @@ class DefaultParcelGenerator(
val floorType = o.floorType
val fillType = o.fillType
delegateWork(0.95) {
for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint()
val y = vec.y
@@ -298,6 +300,17 @@ class DefaultParcelGenerator(
}
}
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> {
/*
* Get the offsets for the world out of the way

View File

@@ -2,12 +2,18 @@ package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.bukkit.Bukkit
import org.bukkit.World
import org.bukkit.WorldCreator
import org.bukkit.entity.Entity
import org.bukkit.util.Vector
import org.joda.time.DateTime
class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
@@ -45,7 +51,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
private fun loadWorlds0() {
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")
return
}
@@ -64,7 +70,8 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
WorldCreator(worldName).generator(generator).createWorld()
}
parcelWorld = ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime,::DefaultParcelContainer)
parcelWorld =
ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime, ::DefaultParcelContainer)
if (!worldExists) {
val time = DateTime.now()
@@ -73,7 +80,8 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
newlyCreatedWorlds.add(parcelWorld)
} else {
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()) {
plugin.launch(Dispatchers.Default) {
plugin.launch {
val migration = plugin.options.migration
if (migration.enabled) {
migration.instance?.newInstance()?.apply {
@@ -155,10 +163,32 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
}
override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? {
val blockManager1 = getWorldById(parcelId1.worldId)?.blockManager ?: return null
val blockManager2 = getWorldById(parcelId2.worldId)?.blockManager ?: return null
val world1 = getWorldById(parcelId1.worldId) ?: 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) {
val temporaryParcel = world1.nextEmptyParcel()
?: world2.nextEmptyParcel()
?: return@trySubmitBlockVisitor
var region1 = blockManager1.getRegion(parcelId1)
var region2 = blockManager2.getRegion(parcelId2)
@@ -168,10 +198,41 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
region2 = region2.withSize(size)
}
val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(blockManager1.world, region1) } }
val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(blockManager2.world, region2) } }
delegateWork(0.25) { with(schematicOf1) { paste(blockManager2.world, region2.origin) } }
delegateWork(0.25) { with(schematicOf2) { paste(blockManager1.world, region1.origin) } }
// Teleporting entities safely requires a different approach:
// * Copy schematic1 into temporary location
// * Teleport entities1 into temporary location
// * 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() {
synchronized {
if (isShutdown || isConnected) throw IllegalStateException()
dataSource = dataSourceFactory()
database = Database.connect(dataSource!!)
transaction(database!!) {
val dataSource = dataSourceFactory()
this.dataSource = dataSource
val database = Database.connect(dataSource)
this.database = database
transaction(database) {
create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT)
}
}
@@ -84,7 +86,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
synchronized {
if (isShutdown) throw IllegalStateException()
isShutdown = true
coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown"))
coroutineContext.cancel(CancellationException("ExposedBacking shutdown"))
dataSource?.let {
(it as? HikariDataSource)?.close()
}

View File

@@ -3,6 +3,7 @@ package io.dico.parcels2.util
import io.dico.parcels2.util.ext.isValid
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import java.lang.IllegalArgumentException
import java.util.UUID
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 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)
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)
infix fun addX(o: Double) = Vec3d(x + o, y, z)
infix fun addY(o: Double) = Vec3d(x, y + o, z)
@@ -50,4 +52,10 @@ data class Vec3d(
Dimension.Y -> addY(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)
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: Vec3d) = Vec3d(x - o.x, y - o.y, z - o.z)
infix fun addX(o: Int) = Vec3i(x + o, y, z)
infix fun addY(o: Int) = Vec3i(x, y + o, z)
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)
Prevent invalid player names from being saved to the database.
Here, invalid player names mean names that contain invalid characters.
~~Prevent invalid player names from being saved to the database.
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
(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
-
Clear (and swap) entities on /p clear etc
~~Clear (and swap) entities on /p clear etc~~
Fix command lag
Chorus fruit can grow outside plots
Vines can grow outside plots