From 8a609a2d067b4a6afee6cc3ddc9ff340131a57ee Mon Sep 17 00:00:00 2001 From: xiaoyu2006 Date: Fri, 11 Feb 2022 17:02:51 +0000 Subject: [PATCH 01/17] Bump to 1.18.1 --- gradle.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 63a458c..f8f4752 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,9 +2,9 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.18 -yarn_mappings=1.18+build.1 -loader_version=0.12.8 +minecraft_version=1.18.1 +yarn_mappings=1.18.1+build.22 +loader_version=0.13.1 # Mod Properties mod_version=1.2.6 maven_group=wearblackallday From 3696a91e1ca1229f7e97e04c382d3241db7fcf64 Mon Sep 17 00:00:00 2001 From: xiaoyu2006 Date: Fri, 11 Feb 2022 17:19:27 +0000 Subject: [PATCH 02/17] Bump loom --- build.gradle | 8 ++++---- src/main/resources/fabric.mod.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index ada4b33..b28ad5b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '0.10-SNAPSHOT' + id 'fabric-loom' version '0.11-SNAPSHOT' id 'maven-publish' } @@ -37,9 +37,9 @@ dependencies { // You may need to force-disable transitiveness on them. } -minecraft { - accessWidener = file("src/main/resources/dimthread.accesswidener") - refmapName = "dimthread.refmap.json" +loom { + accessWidenerPath = file("src/main/resources/dimthread.accesswidener") + mixin.defaultRefmapName = "dimthread.refmap.json" } processResources { diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 2a2aae5..47498e3 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -17,7 +17,7 @@ ], "accessWidener": "dimthread.accesswidener", "depends": { - "fabricloader": ">=0.12.8", + "fabricloader": ">=0.13.1", "minecraft": ">=1.18" } } From e0560616197c75601a86fac09d68ff84a1ebc4fc Mon Sep 17 00:00:00 2001 From: xiaoyu2006 Date: Thu, 17 Feb 2022 00:38:16 +0800 Subject: [PATCH 03/17] Attempt --- .gitignore | 1 + .../dimthread/mixin/EntityMixin.java | 37 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 5b89b33..c611735 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # User-specific stuff .idea/ +/.vscode *.iml *.ipr diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index 1203f8f..ef6f899 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -1,18 +1,26 @@ package wearblackallday.dimthread.mixin; -import wearblackallday.dimthread.DimThread; -import net.minecraft.entity.Entity; -import net.minecraft.server.world.ServerWorld; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import net.minecraft.entity.Entity; +import net.minecraft.entity.FallingBlockEntity; +import net.minecraft.server.world.ServerWorld; +import wearblackallday.dimthread.DimThread; + @Mixin(Entity.class) public abstract class EntityMixin { @Shadow public abstract Entity moveToWorld(ServerWorld destination); + @Shadow @Final public abstract boolean isRemoved(); + @Shadow private Entity.RemovalReason removalReason; + + public Entity.RemovalReason formerRemovalReason = null; /** * Schedules moving entities between dimensions to the server thread. Once all the world finish ticking, @@ -31,4 +39,27 @@ public void moveToWorld(ServerWorld destination, CallbackInfoReturnable } } + /** + * If in the moveToWorld method removalReason is DISCARDED, then we assume its caused by a + * sand-duper and set the removalReason to null to trick the game it's not removed and hopefully + * we won't mess up with the newly-copied entity. + */ + @Redirect(method = "moveToWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isRemoved()Z")) + public final boolean forceNotRemoved(Entity entity) { + Entity.RemovalReason reason = this.removalReason; + if (((Object) this) instanceof FallingBlockEntity && reason == Entity.RemovalReason.DISCARDED) { + this.formerRemovalReason = reason; + reason = null; + return false; + } + return this.isRemoved(); + } + + @Inject(method = "moveToWorld", at = @At("TAIL")) + public void restoreRemovalReason(ServerWorld destination, CallbackInfoReturnable ci) { + if (this.formerRemovalReason != null) { + this.removalReason = this.formerRemovalReason; + this.formerRemovalReason = null; + } + } } From 472ff453a2a7dcf20f2bf463315eb7b1961de006 Mon Sep 17 00:00:00 2001 From: xiaoyu2006 Date: Thu, 17 Feb 2022 21:13:25 +0800 Subject: [PATCH 04/17] Simpify --- gradle.properties | 2 +- .../dimthread/mixin/EntityMixin.java | 42 ++++++++----------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/gradle.properties b/gradle.properties index f8f4752..f1ef3b6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.18.1 yarn_mappings=1.18.1+build.22 loader_version=0.13.1 # Mod Properties -mod_version=1.2.6 +mod_version=1.2.6-sand-duper-fix maven_group=wearblackallday archives_base_name=DimThread # Dependencies diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index ef6f899..85a7a4d 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -17,49 +17,41 @@ public abstract class EntityMixin { @Shadow public abstract Entity moveToWorld(ServerWorld destination); - @Shadow @Final public abstract boolean isRemoved(); - @Shadow private Entity.RemovalReason removalReason; - public Entity.RemovalReason formerRemovalReason = null; + @Shadow @Final public abstract boolean isRemoved(); + + @Shadow private Entity.RemovalReason removalReason; /** - * Schedules moving entities between dimensions to the server thread. Once all the world finish ticking, - * {@code moveToWorld()} is processed in a safe manner avoiding concurrent modification exceptions. + * Schedules moving entities between dimensions to the server thread. Once all + * the world finish ticking, {@code moveToWorld()} is processed in a safe manner + * avoiding concurrent modification exceptions. * - * For example, the entity list is not thread-safe and modifying it from multiple threads will cause - * a crash. Additionally, loading chunks from another thread will cause a deadlock in the server chunk manager. - * */ + * For example, the entity list is not thread-safe and modifying it from + * multiple threads will cause a crash. Additionally, loading chunks from + * another thread will cause a deadlock in the server chunk manager. + */ @Inject(method = "moveToWorld", at = @At("HEAD"), cancellable = true) public void moveToWorld(ServerWorld destination, CallbackInfoReturnable ci) { - if(!DimThread.MANAGER.isActive(destination.getServer()))return; + if (!DimThread.MANAGER.isActive(destination.getServer())) + return; - if(DimThread.owns(Thread.currentThread())) { + if (DimThread.owns(Thread.currentThread())) { destination.getServer().execute(() -> this.moveToWorld(destination)); ci.setReturnValue(null); } } /** - * If in the moveToWorld method removalReason is DISCARDED, then we assume its caused by a - * sand-duper and set the removalReason to null to trick the game it's not removed and hopefully - * we won't mess up with the newly-copied entity. + * If in the moveToWorld method removalReason is DISCARDED, then we assume its + * caused by a sand duplicator. */ @Redirect(method = "moveToWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isRemoved()Z")) public final boolean forceNotRemoved(Entity entity) { Entity.RemovalReason reason = this.removalReason; - if (((Object) this) instanceof FallingBlockEntity && reason == Entity.RemovalReason.DISCARDED) { - this.formerRemovalReason = reason; - reason = null; + if ((((Object) this) instanceof FallingBlockEntity) && (reason == Entity.RemovalReason.DISCARDED)) { return false; } - return this.isRemoved(); - } - - @Inject(method = "moveToWorld", at = @At("TAIL")) - public void restoreRemovalReason(ServerWorld destination, CallbackInfoReturnable ci) { - if (this.formerRemovalReason != null) { - this.removalReason = this.formerRemovalReason; - this.formerRemovalReason = null; - } + return entity.isRemoved(); } } From 923291e6cb0902bd47587bd51f6757511512a189 Mon Sep 17 00:00:00 2001 From: xiaoyu2006 Date: Fri, 18 Feb 2022 21:31:51 +0800 Subject: [PATCH 05/17] I have no idea why this works --- .../dimthread/mixin/EntityMixin.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index 85a7a4d..75b92cb 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -1,5 +1,8 @@ package wearblackallday.dimthread.mixin; +import net.minecraft.entity.Entity; +import net.minecraft.entity.FallingBlockEntity; +import net.minecraft.server.world.ServerWorld; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -7,10 +10,6 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import net.minecraft.entity.Entity; -import net.minecraft.entity.FallingBlockEntity; -import net.minecraft.server.world.ServerWorld; import wearblackallday.dimthread.DimThread; @Mixin(Entity.class) @@ -22,6 +21,8 @@ public abstract class EntityMixin { @Shadow private Entity.RemovalReason removalReason; + private boolean duped = false; + /** * Schedules moving entities between dimensions to the server thread. Once all * the world finish ticking, {@code moveToWorld()} is processed in a safe manner @@ -49,7 +50,12 @@ public void moveToWorld(ServerWorld destination, CallbackInfoReturnable @Redirect(method = "moveToWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isRemoved()Z")) public final boolean forceNotRemoved(Entity entity) { Entity.RemovalReason reason = this.removalReason; - if ((((Object) this) instanceof FallingBlockEntity) && (reason == Entity.RemovalReason.DISCARDED)) { + if ( + (Entity) (Object) this instanceof FallingBlockEntity && + reason == Entity.RemovalReason.DISCARDED && + !duped + ) { + duped = true; return false; } return entity.isRemoved(); From 59f36f1e7f1cfcf15942c5d99bfbbd410935fd52 Mon Sep 17 00:00:00 2001 From: xiaoyu2006 Date: Sun, 13 Mar 2022 09:53:44 +0000 Subject: [PATCH 06/17] Bump version --- README.md | 4 +- gradle.properties | 6 +- gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 257 +++++++++++------- gradlew.bat | 21 +- .../mixin/RedstoneWireBlockMixin.java | 4 +- 7 files changed, 163 insertions(+), 131 deletions(-) diff --git a/README.md b/README.md index 0c87024..700fa8b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ It works on both the **client and server**, and **does not** require the mod to ### Downloads -You can find downloads for DimThread on the [GitHub releases page](https://github.com/WearBlackAllDay/DimensionalThreading/releases). +You can find downloads for DimThread on the [GitHub releases page](https://github.com/MCTown/DimensionalThreading/releases). The Fabric API can be found on [Curseforge](https://www.curseforge.com/minecraft/mc-mods/fabric-api). ### Installing @@ -41,8 +41,6 @@ The mod will not crash if you do not have enough threads available, but it will ##### How is the compatibility with other mods? Compatibility with [JellySquids](https://github.com/jellysquid3) performance mods and [Carpet](https://github.com/gnembon/fabric-carpet) will be ensured and issues concerning them are accepted. If you plan on using a different mod compatibility cannot be guaranteed, since not every author writes their mod threadsafe. -##### What about older versions of Minecraft? -DimThread is written with 1.16 compatibility in mind, older versions are currently not considered. If you play on 1.12 you can use a similar project maintained by [2No2Name](https://github.com/2No2Name). --- diff --git a/gradle.properties b/gradle.properties index f1ef3b6..5aa7daf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,9 +2,9 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.18.1 -yarn_mappings=1.18.1+build.22 -loader_version=0.13.1 +minecraft_version=1.18.2 +yarn_mappings=1.18.2+build.2 +loader_version=0.13.3 # Mod Properties mod_version=1.2.6-sand-duper-fix maven_group=wearblackallday diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 20829 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
;BmaEf^h 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index a9f778a..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/wearblackallday/dimthread/mixin/RedstoneWireBlockMixin.java b/src/main/java/wearblackallday/dimthread/mixin/RedstoneWireBlockMixin.java index 919c878..4965042 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/RedstoneWireBlockMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/RedstoneWireBlockMixin.java @@ -25,7 +25,7 @@ public abstract class RedstoneWireBlockMixin { @Shadow @Final public static IntProperty POWER; @Shadow @Final public static Map> DIRECTION_TO_WIRE_CONNECTION_PROPERTY; - @Shadow protected abstract BlockState method_27840(BlockView world, BlockState state, BlockPos pos); + @Shadow protected abstract BlockState getPlacementState(BlockView world, BlockState state, BlockPos pos); /** * {@code RedstoneWireBlock#wiresGivePower} is not thread-safe since it's a global flag. To ensure @@ -79,7 +79,7 @@ public int getWeakRedstonePower(BlockState state, BlockView world, BlockPos pos, int i = state.get(POWER); if(i == 0)return 0; - return direction != Direction.UP && !this.method_27840(world, state, pos) + return direction != Direction.UP && !this.getPlacementState(world, state, pos) .get(DIRECTION_TO_WIRE_CONNECTION_PROPERTY.get(direction.getOpposite())).isConnected() ? 0 : i; } From 8c0cadf761b32d0dd19aa547af550ff41ba2b0e4 Mon Sep 17 00:00:00 2001 From: xiaoyu2006 Date: Wed, 16 Mar 2022 09:56:37 +0000 Subject: [PATCH 07/17] Bump version --- build.gradle | 6 +++--- gradle.properties | 6 +++--- .../java/wearblackallday/dimthread/mixin/EntityMixin.java | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index b28ad5b..67ab15d 100644 --- a/build.gradle +++ b/build.gradle @@ -31,8 +31,8 @@ dependencies { modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" // Fabric API. This is technically optional, but you probably want it anyway. -// modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - modImplementation"net.fabricmc.fabric-api:fabric-game-rule-api-v1:${project.fabric_game_rule_v1_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + // modImplementation"net.fabricmc.fabric-api:fabric-game-rule-api-v1:${project.fabric_game_rule_v1_version}" // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. } @@ -58,7 +58,7 @@ tasks.withType(JavaCompile).configureEach { it.options.encoding = "UTF-8" // Minecraft 1.17 (21w19a) upwards uses Java 16. - it.options.release = 16 + it.options.release = 17 } java { diff --git a/gradle.properties b/gradle.properties index 5aa7daf..1f064ea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,11 +6,11 @@ minecraft_version=1.18.2 yarn_mappings=1.18.2+build.2 loader_version=0.13.3 # Mod Properties -mod_version=1.2.6-sand-duper-fix +mod_version=1.2.6-1.18.2-sand-duper-fix maven_group=wearblackallday archives_base_name=DimThread # Dependencies # check this on https://modmuss50.me/fabric.html -#fabric_version=0.44.0+1.18 -fabric_game_rule_v1_version=1.0.9+e77d3ea6cb +fabric_version=0.48.0+1.18.2 +#fabric_game_rule_v1_version=1.0.9+e77d3ea6cb diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index 75b92cb..7a84807 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -48,16 +48,16 @@ public void moveToWorld(ServerWorld destination, CallbackInfoReturnable * caused by a sand duplicator. */ @Redirect(method = "moveToWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isRemoved()Z")) - public final boolean forceNotRemoved(Entity entity) { + public final boolean forceNotRemoved(Entity self) { Entity.RemovalReason reason = this.removalReason; if ( - (Entity) (Object) this instanceof FallingBlockEntity && + self instanceof FallingBlockEntity && reason == Entity.RemovalReason.DISCARDED && !duped ) { duped = true; return false; } - return entity.isRemoved(); + return self.isRemoved(); } } From bcfa02274b56860eeb713b1399c24734a6cd1b49 Mon Sep 17 00:00:00 2001 From: Johannes7k75 <57235955+Johannes7k75@users.noreply.github.com> Date: Wed, 20 Apr 2022 17:45:44 +0200 Subject: [PATCH 08/17] Create build.yml --- .github/workflows/build.yml | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1fc365f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,39 @@ +# Automatically build the project and run any configured tests for every push +# and submitted pull request. This can help catch issues that only occur on +# certain platforms or Java versions, and provides a first line of defence +# against bad commits. + +name: build +on: [pull_request, push] + +jobs: + build: + strategy: + matrix: + # Use these Java versions + java: [ + 17, # Current Java LTS & minimum supported by Minecraft + ] + # and run on both Linux and Windows + os: [ubuntu-20.04, windows-2022] + runs-on: ${{ matrix.os }} + steps: + - name: checkout repository + uses: actions/checkout@v2 + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 + - name: setup jdk ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + - name: make gradle wrapper executable + if: ${{ runner.os != 'Windows' }} + run: chmod +x ./gradlew + - name: build + run: ./gradlew build + - name: capture build artifacts + if: ${{ runner.os == 'Linux' && matrix.java == '17' }} # Only upload artifacts built from latest java on one OS + uses: actions/upload-artifact@v2 + with: + name: Artifacts + path: build/libs/ From a912fcf0c96ef37b2ec07ce78996d8571a62c6ab Mon Sep 17 00:00:00 2001 From: Yi Date: Wed, 20 Apr 2022 21:32:27 +0800 Subject: [PATCH 09/17] Attempt 2 --- build.gradle | 2 +- gradle.properties | 6 +-- .../dimthread/mixin/EntityMixin.java | 41 +++++++------------ src/main/resources/dimthread.accesswidener | 2 +- 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/build.gradle b/build.gradle index 67ab15d..96577a6 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ repositories { dependencies { implementation "com.github.wearblackallday:JavaUtils:bd58640372" - include"com.github.wearblackallday:JavaUtils:bd58640372" + include "com.github.wearblackallday:JavaUtils:bd58640372" // To change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" diff --git a/gradle.properties b/gradle.properties index 1f064ea..89b6ed3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,14 +3,14 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html minecraft_version=1.18.2 -yarn_mappings=1.18.2+build.2 +yarn_mappings=1.18.2+build.3 loader_version=0.13.3 # Mod Properties -mod_version=1.2.6-1.18.2-sand-duper-fix +mod_version=1.2.6-1.18.2-sand-duper-fix.2 maven_group=wearblackallday archives_base_name=DimThread # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.48.0+1.18.2 +fabric_version=0.51.1+1.18.2 #fabric_game_rule_v1_version=1.0.9+e77d3ea6cb diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index 7a84807..2f44328 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -12,16 +12,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import wearblackallday.dimthread.DimThread; -@Mixin(Entity.class) -public abstract class EntityMixin { - - @Shadow public abstract Entity moveToWorld(ServerWorld destination); - - @Shadow @Final public abstract boolean isRemoved(); - - @Shadow private Entity.RemovalReason removalReason; +import org.slf4j.LoggerFactory; - private boolean duped = false; +@Mixin(Entity.class) +public abstract class EntityMixin implements Cloneable { /** * Schedules moving entities between dimensions to the server thread. Once all @@ -38,26 +32,21 @@ public void moveToWorld(ServerWorld destination, CallbackInfoReturnable return; if (DimThread.owns(Thread.currentThread())) { - destination.getServer().execute(() -> this.moveToWorld(destination)); + Entity snapshot = null; + try { + snapshot = (Entity) (this.clone()); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + final Entity finalSnapshot = snapshot; + destination.getServer().execute( + () -> finalSnapshot.moveToWorld(destination) + ); ci.setReturnValue(null); } } - /** - * If in the moveToWorld method removalReason is DISCARDED, then we assume its - * caused by a sand duplicator. - */ - @Redirect(method = "moveToWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isRemoved()Z")) - public final boolean forceNotRemoved(Entity self) { - Entity.RemovalReason reason = this.removalReason; - if ( - self instanceof FallingBlockEntity && - reason == Entity.RemovalReason.DISCARDED && - !duped - ) { - duped = true; - return false; - } - return self.isRemoved(); + protected Object clone() throws CloneNotSupportedException { + return super.clone(); } } diff --git a/src/main/resources/dimthread.accesswidener b/src/main/resources/dimthread.accesswidener index 46ccd35..027fe2d 100644 --- a/src/main/resources/dimthread.accesswidener +++ b/src/main/resources/dimthread.accesswidener @@ -1,2 +1,2 @@ accessWidener v1 named -accessible class net/minecraft/server/world/ServerChunkManager$MainThreadExecutor \ No newline at end of file +accessible class net/minecraft/server/world/ServerChunkManager$MainThreadExecutor From 12eae97a486f0a56f56dfcecbb5ce0105b6bed4b Mon Sep 17 00:00:00 2001 From: Yi Cao Date: Sat, 23 Apr 2022 21:37:04 +0800 Subject: [PATCH 10/17] Attempt 3 --- .../dimthread/mixin/EntityMixin.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index 2f44328..eeeaecf 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -5,10 +5,12 @@ import net.minecraft.server.world.ServerWorld; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import wearblackallday.dimthread.DimThread; @@ -17,6 +19,11 @@ @Mixin(Entity.class) public abstract class EntityMixin implements Cloneable { + public boolean isCloned = false; + + @Shadow @Final + abstract void setRemoved(Entity.RemovalReason reason); + /** * Schedules moving entities between dimensions to the server thread. Once all * the world finish ticking, {@code moveToWorld()} is processed in a safe manner @@ -42,11 +49,26 @@ public void moveToWorld(ServerWorld destination, CallbackInfoReturnable destination.getServer().execute( () -> finalSnapshot.moveToWorld(destination) ); + this.removeFromDimension(); ci.setReturnValue(null); } } + /** + * @author xiaoyu2006 + * @reason If this is a cloned entity, it should not execute removeFromDimension. + */ + @Overwrite + public void removeFromDimension() { + if (this.isCloned) { + return; + } + this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION); + } + protected Object clone() throws CloneNotSupportedException { - return super.clone(); + EntityMixin cloned = (EntityMixin) super.clone(); + cloned.isCloned = true; + return cloned; } } From d3d5b136ab471de99cbd52842eb148e9c4229180 Mon Sep 17 00:00:00 2001 From: xiaoyu2006 Date: Sat, 4 Jun 2022 11:41:13 +0000 Subject: [PATCH 11/17] Bump version --- build.gradle | 8 +- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../wearblackallday/dimthread/DimThread.java | 2 +- .../dimthread/mixin/EntityMixin.java | 12 +- .../dimthread/mixin/MinecraftServerMixin.java | 2 +- .../dimthread/util/ServerManager.java | 2 +- .../dimthread/util/ThreadPool.java | 169 ++++++++++++++++++ src/main/resources/dimthread.mixins.json | 2 +- src/main/resources/fabric.mod.json | 2 +- 10 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 src/main/java/wearblackallday/dimthread/util/ThreadPool.java diff --git a/build.gradle b/build.gradle index 96577a6..ee79610 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '0.11-SNAPSHOT' + id 'fabric-loom' version '0.12-SNAPSHOT' id 'maven-publish' } @@ -11,9 +11,6 @@ version = project.mod_version group = project.maven_group repositories { - maven { - url "https://jitpack.io" - } // Add repositories to retrieve artifacts from in here. // You should only use this when depending on other mods because // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. @@ -22,9 +19,6 @@ repositories { } dependencies { - implementation "com.github.wearblackallday:JavaUtils:bd58640372" - include "com.github.wearblackallday:JavaUtils:bd58640372" - // To change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" diff --git a/gradle.properties b/gradle.properties index 89b6ed3..fddc475 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.18.2 yarn_mappings=1.18.2+build.3 loader_version=0.13.3 # Mod Properties -mod_version=1.2.6-1.18.2-sand-duper-fix.2 +mod_version=1.2.6-1.18.2-sand-duper-fix.3 maven_group=wearblackallday archives_base_name=DimThread # Dependencies diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 00e33ed..aa991fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/wearblackallday/dimthread/DimThread.java b/src/main/java/wearblackallday/dimthread/DimThread.java index 5d79bb2..8aa0bd1 100644 --- a/src/main/java/wearblackallday/dimthread/DimThread.java +++ b/src/main/java/wearblackallday/dimthread/DimThread.java @@ -6,7 +6,7 @@ import wearblackallday.dimthread.init.ModGameRules; import wearblackallday.dimthread.thread.IMutableMainThread; import wearblackallday.dimthread.util.ServerManager; -import wearblackallday.util.ThreadPool; +import wearblackallday.dimthread.util.ThreadPool; public class DimThread implements ModInitializer { diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index eeeaecf..d7f8f6d 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -5,11 +5,9 @@ import net.minecraft.server.world.ServerWorld; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import wearblackallday.dimthread.DimThread; @@ -24,6 +22,9 @@ public abstract class EntityMixin implements Cloneable { @Shadow @Final abstract void setRemoved(Entity.RemovalReason reason); + @Shadow + abstract void removeFromDimension(); + /** * Schedules moving entities between dimensions to the server thread. Once all * the world finish ticking, {@code moveToWorld()} is processed in a safe manner @@ -58,12 +59,11 @@ public void moveToWorld(ServerWorld destination, CallbackInfoReturnable * @author xiaoyu2006 * @reason If this is a cloned entity, it should not execute removeFromDimension. */ - @Overwrite - public void removeFromDimension() { + @Inject(method = "removeFromDimension", at = @At("HEAD"), cancellable = true) + public void clonedDoNotRemove(CallbackInfo ci) { if (this.isCloned) { - return; + ci.cancel(); } - this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION); } protected Object clone() throws CloneNotSupportedException { diff --git a/src/main/java/wearblackallday/dimthread/mixin/MinecraftServerMixin.java b/src/main/java/wearblackallday/dimthread/mixin/MinecraftServerMixin.java index 7faeb4f..64d1418 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/MinecraftServerMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/MinecraftServerMixin.java @@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import wearblackallday.dimthread.DimThread; import wearblackallday.dimthread.util.CrashInfo; -import wearblackallday.util.ThreadPool; +import wearblackallday.dimthread.util.ThreadPool; import java.util.Collections; import java.util.Iterator; diff --git a/src/main/java/wearblackallday/dimthread/util/ServerManager.java b/src/main/java/wearblackallday/dimthread/util/ServerManager.java index af399bd..2da7f2a 100644 --- a/src/main/java/wearblackallday/dimthread/util/ServerManager.java +++ b/src/main/java/wearblackallday/dimthread/util/ServerManager.java @@ -3,7 +3,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.world.GameRules; import wearblackallday.dimthread.init.ModGameRules; -import wearblackallday.util.ThreadPool; +import wearblackallday.dimthread.util.ThreadPool; import java.util.Collections; import java.util.ConcurrentModificationException; diff --git a/src/main/java/wearblackallday/dimthread/util/ThreadPool.java b/src/main/java/wearblackallday/dimthread/util/ThreadPool.java new file mode 100644 index 0000000..97fb3c1 --- /dev/null +++ b/src/main/java/wearblackallday/dimthread/util/ThreadPool.java @@ -0,0 +1,169 @@ +package wearblackallday.dimthread.util; + +import java.util.Iterator; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.function.*; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +public class ThreadPool { + private ThreadPoolExecutor executor; + private final int threadCount; + private final IntLatch activeCount = new IntLatch(); + + public ThreadPool() { + this(Runtime.getRuntime().availableProcessors()); + } + + public ThreadPool(int threadCount) { + this.threadCount = threadCount; + this.restart(); + } + + public int getThreadCount() { + return this.threadCount; + } + + public int getActiveCount() { + return this.activeCount.getCount(); + } + + public ThreadPoolExecutor getExecutor() { + return this.executor; + } + + public void execute(Runnable action) { + this.activeCount.increment(); + + this.executor.execute(() -> { + action.run(); + this.activeCount.decrement(); + }); + } + + public void execute(Iterator iterator, Consumer action) { + iterator.forEachRemaining(t -> this.execute(() -> action.accept(t))); + } + + public void execute(Iterable iterable, Consumer action) { + iterable.forEach(t -> this.execute(() -> action.accept(t))); + } + + public void execute(Stream stream, Consumer action) { + stream.forEach(t -> this.execute(() -> action.accept(t))); + } + + public void execute(IntStream stream, IntConsumer action) { + stream.forEach(t -> this.execute(() -> action.accept(t))); + } + + public void execute(LongStream stream, LongConsumer action) { + stream.forEach(t -> this.execute(() -> action.accept(t))); + } + + public void execute(DoubleStream stream, DoubleConsumer action) { + stream.forEach(t -> this.execute(() -> action.accept(t))); + } + + public void execute(T[] array, Consumer action) { + for(T t : array) this.execute(() -> action.accept(t)); + } + + public void execute(boolean[] array, Consumer action) { + for(boolean t : array) this.execute(() -> action.accept(t)); + } + + public void execute(byte[] array, Consumer action) { + for(byte t : array) this.execute(() -> action.accept(t)); + } + + public void execute(short[] array, Consumer action) { + for(short t : array) this.execute(() -> action.accept(t)); + } + + public void execute(int[] array, IntConsumer action) { + for(int t : array) this.execute(() -> action.accept(t)); + } + + public void execute(float[] array, Consumer action) { + for(float t : array) this.execute(() -> action.accept(t)); + } + + public void execute(long[] array, LongConsumer action) { + for(long t : array) this.execute(() -> action.accept(t)); + } + + public void execute(double[] array, DoubleConsumer action) { + for(double t : array) this.execute(() -> action.accept(t)); + } + + public void execute(char[] array, Consumer action) { + for(char t : array) this.execute(() -> action.accept(t)); + } + + public void awaitFreeThread() { + this.waitFor(value -> value < this.getThreadCount()); + } + + public void awaitCompletion() { + this.waitFor(value -> value == 0); + } + + public void waitFor(IntPredicate condition) { + try { + this.activeCount.waitUntil(condition); + } catch(InterruptedException e) { + e.printStackTrace(); + } + } + + public void restart() { + if(this.executor == null || this.executor.isShutdown()) { + this.executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(this.threadCount); + } + } + + public void shutdown() { + this.executor.shutdown(); + } + + public boolean isShutdown() { + return this.executor.isShutdown(); + } + + private static class IntLatch { + private CountDownLatch latch; + + private IntLatch() { + this(0); + } + + private IntLatch(int count) { + this.latch = new CountDownLatch(count); + } + + private synchronized int getCount() { + return (int)this.latch.getCount(); + } + + private synchronized void decrement() { + this.latch.countDown(); + this.notifyAll(); + } + + private synchronized void increment() { + this.latch = new CountDownLatch((int)this.latch.getCount() + 1); + this.notifyAll(); + } + + private synchronized void waitUntil(IntPredicate predicate) throws InterruptedException { + while(!predicate.test(this.getCount())) { + this.wait(); + } + } + } +} diff --git a/src/main/resources/dimthread.mixins.json b/src/main/resources/dimthread.mixins.json index dae46c2..60c38f2 100644 --- a/src/main/resources/dimthread.mixins.json +++ b/src/main/resources/dimthread.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "wearblackallday.dimthread.mixin", - "compatibilityLevel": "JAVA_16", + "compatibilityLevel": "JAVA_17", "mixins": [ "EntityMixin", "MinecraftServerMixin", diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 47498e3..cb2f68e 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -17,7 +17,7 @@ ], "accessWidener": "dimthread.accesswidener", "depends": { - "fabricloader": ">=0.13.1", + "fabricloader": ">=0.13.3", "minecraft": ">=1.18" } } From d9218ecb5c7012c8031d5171e898fbbd495477b5 Mon Sep 17 00:00:00 2001 From: xiaoyu2006 Date: Sat, 4 Jun 2022 11:49:13 +0000 Subject: [PATCH 12/17] Add fabric-carpet as RuntimeOnly dep To make life easier --- .github/workflows/build.yml | 14 +++++++------- build.gradle | 7 ++----- gradle.properties | 1 + 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1fc365f..ae2282e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,21 +18,21 @@ jobs: os: [ubuntu-20.04, windows-2022] runs-on: ${{ matrix.os }} steps: - - name: checkout repository + - name: Checkout uses: actions/checkout@v2 - - name: validate gradle wrapper + - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - - name: setup jdk ${{ matrix.java }} + - name: Setup JDK ${{ matrix.java }} uses: actions/setup-java@v1 with: java-version: ${{ matrix.java }} - - name: make gradle wrapper executable + - name: Make Wrapper Executable if: ${{ runner.os != 'Windows' }} run: chmod +x ./gradlew - - name: build + - name: Build Project run: ./gradlew build - - name: capture build artifacts - if: ${{ runner.os == 'Linux' && matrix.java == '17' }} # Only upload artifacts built from latest java on one OS + - name: Capture Build Artifacts + if: ${{ runner.os == 'Linux' && matrix.java == '17' }} uses: actions/upload-artifact@v2 with: name: Artifacts diff --git a/build.gradle b/build.gradle index ee79610..54dca12 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ repositories { // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. // See https://docs.gradle.org/current/userguide/declaring_repositories.html // for more information about repositories. + maven { url "https://masa.dy.fi/maven" } } dependencies { @@ -23,12 +24,8 @@ dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - // modImplementation"net.fabricmc.fabric-api:fabric-game-rule-api-v1:${project.fabric_game_rule_v1_version}" - // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. - // You may need to force-disable transitiveness on them. + modRuntimeOnly "carpet:fabric-carpet:${project.minecraft_version}-${project.carpet_version}" } loom { diff --git a/gradle.properties b/gradle.properties index fddc475..25fab1f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,5 +12,6 @@ archives_base_name=DimThread # Dependencies # check this on https://modmuss50.me/fabric.html fabric_version=0.51.1+1.18.2 +carpet_version=1.4.69+v220331 #fabric_game_rule_v1_version=1.0.9+e77d3ea6cb From 8eb70c92b19f1a69942dfa11780a135049b83cdd Mon Sep 17 00:00:00 2001 From: CaveNightingale Date: Mon, 3 Oct 2022 11:55:26 +0800 Subject: [PATCH 13/17] Fix the bug that handed item disappeared when teleporting via nether portal --- .../dimthread/mixin/EntityMixin.java | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index d7f8f6d..acd1c1c 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -1,30 +1,42 @@ package wearblackallday.dimthread.mixin; import net.minecraft.entity.Entity; -import net.minecraft.entity.FallingBlockEntity; +import net.minecraft.entity.EntityType; +import net.minecraft.nbt.NbtCompound; import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import wearblackallday.dimthread.DimThread; -import org.slf4j.LoggerFactory; - @Mixin(Entity.class) -public abstract class EntityMixin implements Cloneable { - - public boolean isCloned = false; - - @Shadow @Final - abstract void setRemoved(Entity.RemovalReason reason); +public abstract class EntityMixin { @Shadow abstract void removeFromDimension(); + private NbtCompound nbtCachedForMoveToWorld; + + @Shadow public abstract World getWorld(); + + @Shadow public abstract NbtCompound writeNbt(NbtCompound nbt); + + @Shadow public abstract @Nullable Entity moveToWorld(ServerWorld destination); + + @Shadow private int netherPortalCooldown; + + @Shadow protected BlockPos lastNetherPortalPosition; + + @Shadow public abstract boolean isRemoved(); + /** * Schedules moving entities between dimensions to the server thread. Once all * the world finish ticking, {@code moveToWorld()} is processed in a safe manner @@ -35,20 +47,14 @@ public abstract class EntityMixin implements Cloneable { * another thread will cause a deadlock in the server chunk manager. */ @Inject(method = "moveToWorld", at = @At("HEAD"), cancellable = true) - public void moveToWorld(ServerWorld destination, CallbackInfoReturnable ci) { + public void onMoveToWorld(ServerWorld destination, CallbackInfoReturnable ci) { if (!DimThread.MANAGER.isActive(destination.getServer())) return; if (DimThread.owns(Thread.currentThread())) { - Entity snapshot = null; - try { - snapshot = (Entity) (this.clone()); - } catch (CloneNotSupportedException e) { - throw new RuntimeException(e); - } - final Entity finalSnapshot = snapshot; + nbtCachedForMoveToWorld = writeNbt(new NbtCompound()); destination.getServer().execute( - () -> finalSnapshot.moveToWorld(destination) + () -> this.moveToWorld(destination) ); this.removeFromDimension(); ci.setReturnValue(null); @@ -56,19 +62,29 @@ public void moveToWorld(ServerWorld destination, CallbackInfoReturnable } /** - * @author xiaoyu2006 - * @reason If this is a cloned entity, it should not execute removeFromDimension. + * Perform deep copy instead of clone() to fix the bug that handed item disappear while teleporting */ + @Redirect(method = "moveToWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;copyFrom(Lnet/minecraft/entity/Entity;)V")) + private void onMoveToWorldCopyFrom(Entity instance, Entity original) { + NbtCompound nbtCompound = nbtCachedForMoveToWorld; + nbtCompound.remove("Dimension"); + instance.readNbt(nbtCompound); + ((EntityMixin) (Object) instance).netherPortalCooldown = ((EntityMixin) (Object) original).netherPortalCooldown; + ((EntityMixin) (Object) instance).lastNetherPortalPosition = ((EntityMixin) (Object) original).lastNetherPortalPosition; + } + @Inject(method = "removeFromDimension", at = @At("HEAD"), cancellable = true) - public void clonedDoNotRemove(CallbackInfo ci) { - if (this.isCloned) { + private void onRemoveFromDimension(CallbackInfo ci) { + if(isRemoved()) { ci.cancel(); } } - protected Object clone() throws CloneNotSupportedException { - EntityMixin cloned = (EntityMixin) super.clone(); - cloned.isCloned = true; - return cloned; + /** + * We check this because when we call this method in getServer().execute(), the entity has already been removed + */ + @Redirect(method = "moveToWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isRemoved()Z")) + private boolean onMoveToWorldIsRemoved(Entity instance) { + return instance.isRemoved() && ((EntityMixin) (Object) instance).nbtCachedForMoveToWorld == null; } } From 9e1c8744627ad42c294936517a9692765d19d24c Mon Sep 17 00:00:00 2001 From: CaveNightingale Date: Fri, 14 Oct 2022 12:29:29 +0800 Subject: [PATCH 14/17] Sand dupe fix --- .../dimthread/mixin/EntityMixin.java | 116 ++++++++++++++++-- .../util/UncompletedTeleportTarget.java | 12 ++ 2 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 src/main/java/wearblackallday/dimthread/util/UncompletedTeleportTarget.java diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index acd1c1c..ce17071 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -1,13 +1,25 @@ package wearblackallday.dimthread.mixin; +import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityType; +import net.minecraft.entity.EntityDimensions; +import net.minecraft.entity.EntityPose; import net.minecraft.nbt.NbtCompound; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.property.Properties; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.BlockLocating; +import net.minecraft.world.Heightmap; +import net.minecraft.world.TeleportTarget; import net.minecraft.world.World; +import net.minecraft.world.border.WorldBorder; +import net.minecraft.world.dimension.AreaHelper; +import net.minecraft.world.dimension.DimensionType; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -16,14 +28,19 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import wearblackallday.dimthread.DimThread; +import wearblackallday.dimthread.util.UncompletedTeleportTarget; + +import java.util.Optional; @Mixin(Entity.class) public abstract class EntityMixin { + private NbtCompound nbtCachedForMoveToWorld; + + private UncompletedTeleportTarget uncompletedTeleportTargetForMoveToWorld; @Shadow - abstract void removeFromDimension(); + protected abstract void removeFromDimension(); - private NbtCompound nbtCachedForMoveToWorld; @Shadow public abstract World getWorld(); @@ -37,6 +54,32 @@ public abstract class EntityMixin { @Shadow public abstract boolean isRemoved(); + @Shadow @Nullable public abstract MinecraftServer getServer(); + + @Shadow public World world; + + @Shadow public abstract double getX(); + + @Shadow public abstract double getY(); + + @Shadow public abstract double getZ(); + + @Shadow protected abstract Optional getPortalRect(ServerWorld destWorld, BlockPos destPos, boolean destIsNether, WorldBorder worldBorder); + + @Shadow protected abstract Vec3d positionInPortal(Direction.Axis portalAxis, BlockLocating.Rectangle portalRect); + + @Shadow public abstract EntityDimensions getDimensions(EntityPose pose); + + @Shadow public abstract EntityPose getPose(); + + @Shadow public abstract Vec3d getVelocity(); + + @Shadow public abstract float getYaw(); + + @Shadow public abstract float getPitch(); + + @Shadow protected abstract @Nullable TeleportTarget getTeleportTarget(ServerWorld destination); + /** * Schedules moving entities between dimensions to the server thread. Once all * the world finish ticking, {@code moveToWorld()} is processed in a safe manner @@ -53,6 +96,7 @@ public void onMoveToWorld(ServerWorld destination, CallbackInfoReturnable this.moveToWorld(destination) ); @@ -66,11 +110,29 @@ public void onMoveToWorld(ServerWorld destination, CallbackInfoReturnable null; + } else { + WorldBorder border = dest.getWorldBorder(); + double scale = DimensionType.getCoordinateScaleFactor(world.getDimension(), dest.getDimension()); + BlockPos target = border.clamp(getX() * scale, getY() * scale, getZ() * scale); + BlockState portalState = world.getBlockState(lastNetherPortalPosition); + Direction.Axis axis; + Vec3d vec3d; + if (portalState.contains(Properties.HORIZONTAL_AXIS)) { + axis = portalState.get(Properties.HORIZONTAL_AXIS); + BlockLocating.Rectangle rectangle = BlockLocating.getLargestRectangle(this.lastNetherPortalPosition, axis, 21, Direction.Axis.Y, 21, (blockPos) -> this.world.getBlockState(blockPos) == portalState); + vec3d = this.positionInPortal(axis, rectangle); + } else { + axis = Direction.Axis.X; + vec3d = new Vec3d(0.5, 0.0, 0.0); + } + return dest1 -> getPortalRect(dest1, target, isNetherReturnPortal, border).map((rect) -> AreaHelper.getNetherTeleportTarget(dest, rect, axis, vec3d, getDimensions(getPose()), velocity, yaw, pitch)).orElse(null); + } + } else { + return dest1 -> { + BlockPos target = isEndPortal ? ServerWorld.END_SPAWN_POS : dest1.getTopPosition(Heightmap.Type.MOTION_BLOCKING_NO_LEAVES, dest1.getSpawnPos()); + return new TeleportTarget(new Vec3d(target.getX() + 0.5, target.getY(), target.getZ() + 0.5), velocity, yaw, pitch); + }; + } + } } diff --git a/src/main/java/wearblackallday/dimthread/util/UncompletedTeleportTarget.java b/src/main/java/wearblackallday/dimthread/util/UncompletedTeleportTarget.java new file mode 100644 index 0000000..8262795 --- /dev/null +++ b/src/main/java/wearblackallday/dimthread/util/UncompletedTeleportTarget.java @@ -0,0 +1,12 @@ +package wearblackallday.dimthread.util; + +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.TeleportTarget; +import org.jetbrains.annotations.Nullable; + +/** + * This is used for teleport target which is not completed right now, we are going to complete it in another thread + */ +public interface UncompletedTeleportTarget { + @Nullable TeleportTarget complete(ServerWorld dest); +} From b08629aa8c4d4030a9c98265b3995fc110e16e87 Mon Sep 17 00:00:00 2001 From: CaveNightingale Date: Fri, 14 Oct 2022 17:02:16 +0800 Subject: [PATCH 15/17] Fix the bug that nether portal teleporting sometimes fails --- .../java/wearblackallday/dimthread/mixin/EntityMixin.java | 7 +++++-- .../dimthread/util/UncompletedTeleportTarget.java | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index ce17071..d8ea5cc 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -150,7 +150,9 @@ private boolean onMoveToWorldIsRemoved(Entity instance) { return instance.isRemoved() && ((EntityMixin) (Object) instance).nbtCachedForMoveToWorld == null; } - // cache some attributes sync + /** + * take a snapshot of some values so that codes modified it later doesn't affect teleporting + */ private UncompletedTeleportTarget createTeleportTargetUncompleted(ServerWorld dest) { boolean isEndReturnPortal = world.getRegistryKey() == World.END && dest.getRegistryKey() == World.OVERWORLD; boolean isEndPortal = dest.getRegistryKey() == World.END; @@ -168,6 +170,7 @@ private UncompletedTeleportTarget createTeleportTargetUncompleted(ServerWorld de BlockState portalState = world.getBlockState(lastNetherPortalPosition); Direction.Axis axis; Vec3d vec3d; + EntityDimensions dimensions = getDimensions(getPose()); if (portalState.contains(Properties.HORIZONTAL_AXIS)) { axis = portalState.get(Properties.HORIZONTAL_AXIS); BlockLocating.Rectangle rectangle = BlockLocating.getLargestRectangle(this.lastNetherPortalPosition, axis, 21, Direction.Axis.Y, 21, (blockPos) -> this.world.getBlockState(blockPos) == portalState); @@ -176,7 +179,7 @@ private UncompletedTeleportTarget createTeleportTargetUncompleted(ServerWorld de axis = Direction.Axis.X; vec3d = new Vec3d(0.5, 0.0, 0.0); } - return dest1 -> getPortalRect(dest1, target, isNetherReturnPortal, border).map((rect) -> AreaHelper.getNetherTeleportTarget(dest, rect, axis, vec3d, getDimensions(getPose()), velocity, yaw, pitch)).orElse(null); + return dest1 -> getPortalRect(dest1, target, isNetherPortal, border).map((rect) -> AreaHelper.getNetherTeleportTarget(dest1, rect, axis, vec3d, dimensions, velocity, yaw, pitch)).orElse(null); } } else { return dest1 -> { diff --git a/src/main/java/wearblackallday/dimthread/util/UncompletedTeleportTarget.java b/src/main/java/wearblackallday/dimthread/util/UncompletedTeleportTarget.java index 8262795..a8a50e7 100644 --- a/src/main/java/wearblackallday/dimthread/util/UncompletedTeleportTarget.java +++ b/src/main/java/wearblackallday/dimthread/util/UncompletedTeleportTarget.java @@ -6,6 +6,7 @@ /** * This is used for teleport target which is not completed right now, we are going to complete it in another thread + * For thread-unsafe operations */ public interface UncompletedTeleportTarget { @Nullable TeleportTarget complete(ServerWorld dest); From 66a519cd561546e25052c12b60024dba66dac568 Mon Sep 17 00:00:00 2001 From: CaveNightingale Date: Fri, 14 Oct 2022 20:40:13 +0800 Subject: [PATCH 16/17] Fix my bug that multiply Y by the scaler while teleporting throught the nether and the overworld & Add entities which fails to teleport back to the world --- .../dimthread/mixin/EntityMixin.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index d8ea5cc..8c1bf93 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -20,6 +20,8 @@ import net.minecraft.world.dimension.DimensionType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -80,6 +82,12 @@ public abstract class EntityMixin { @Shadow protected abstract @Nullable TeleportTarget getTeleportTarget(ServerWorld destination); + @Shadow protected abstract void unsetRemoved(); + + @Shadow public abstract void readNbt(NbtCompound nbt); + + @Shadow @Final private static Logger LOGGER; + /** * Schedules moving entities between dimensions to the server thread. Once all * the world finish ticking, {@code moveToWorld()} is processed in a safe manner @@ -98,7 +106,18 @@ public void onMoveToWorld(ServerWorld destination, CallbackInfoReturnable this.moveToWorld(destination) + () -> { + Entity entity = this.moveToWorld(destination); + if(entity == null) { + this.unsetRemoved(); + nbtCachedForMoveToWorld.putInt("PortalCooldown", this.netherPortalCooldown); + this.readNbt(nbtCachedForMoveToWorld); + this.uncompletedTeleportTargetForMoveToWorld = null; + this.nbtCachedForMoveToWorld = null; + this.world.spawnEntity((Entity) (Object) this);// if the teleporting failed, we need to add it back to the world + LOGGER.debug("Failed to teleport {}, return it to it's previous world", this); + } + } ); this.removeFromDimension(); ci.setReturnValue(null); @@ -166,7 +185,7 @@ private UncompletedTeleportTarget createTeleportTargetUncompleted(ServerWorld de } else { WorldBorder border = dest.getWorldBorder(); double scale = DimensionType.getCoordinateScaleFactor(world.getDimension(), dest.getDimension()); - BlockPos target = border.clamp(getX() * scale, getY() * scale, getZ() * scale); + BlockPos target = border.clamp(getX() * scale, getY(), getZ() * scale); BlockState portalState = world.getBlockState(lastNetherPortalPosition); Direction.Axis axis; Vec3d vec3d; From ff3c9a9d9e78c87f8b05605793d377ddff3060d0 Mon Sep 17 00:00:00 2001 From: CaveNightingale Date: Sat, 15 Oct 2022 20:42:21 +0800 Subject: [PATCH 17/17] Fix https://github.com/WearBlackAllDay/DimensionalThreading/issues/72. Time ticking is not thread-safe and cause piston problems on worlds that does not tick the time --- .../dimthread/mixin/EntityMixin.java | 2 +- .../dimthread/mixin/MinecraftServerMixin.java | 2 + .../dimthread/mixin/ServerWorldMixin.java | 51 +++++++++++++++++++ .../dimthread/util/ServerWorldAccessor.java | 8 +++ src/main/resources/dimthread.mixins.json | 1 + 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/main/java/wearblackallday/dimthread/mixin/ServerWorldMixin.java create mode 100644 src/main/java/wearblackallday/dimthread/util/ServerWorldAccessor.java diff --git a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java index 8c1bf93..4138b49 100644 --- a/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java +++ b/src/main/java/wearblackallday/dimthread/mixin/EntityMixin.java @@ -115,7 +115,7 @@ public void onMoveToWorld(ServerWorld destination, CallbackInfoReturnable ((ServerWorldAccessor) world).dimthread_tickTime()); // Time ticking is not thread-safe, fix https://github.com/WearBlackAllDay/DimensionalThreading/issues/72 if(crash.get() != null) { crash.get().crash("Exception ticking world"); diff --git a/src/main/java/wearblackallday/dimthread/mixin/ServerWorldMixin.java b/src/main/java/wearblackallday/dimthread/mixin/ServerWorldMixin.java new file mode 100644 index 0000000..34c2fa0 --- /dev/null +++ b/src/main/java/wearblackallday/dimthread/mixin/ServerWorldMixin.java @@ -0,0 +1,51 @@ +package wearblackallday.dimthread.mixin; + +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.profiler.Profiler; +import net.minecraft.util.registry.RegistryEntry; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.MutableWorldProperties; +import net.minecraft.world.World; +import net.minecraft.world.dimension.DimensionType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import wearblackallday.dimthread.DimThread; +import wearblackallday.dimthread.util.ServerWorldAccessor; + +import java.util.function.Supplier; + +@Mixin(ServerWorld.class) +public abstract class ServerWorldMixin extends World implements ServerWorldAccessor { + protected ServerWorldMixin(MutableWorldProperties properties, RegistryKey registryRef, RegistryEntry registryEntry, Supplier profiler, boolean isClient, boolean debugWorld, long seed) { + super(properties, registryRef, registryEntry, profiler, isClient, debugWorld, seed); + } + + @Shadow protected abstract void tickTime(); + + boolean onMainThread = false; + boolean timeTickedOnWorldThread = false; + + /** + * Time ticking is not thread-safe. We cancel time ticking from the world thread. However, DimThread will tick time on the main thread + */ + @Inject(method = "tickTime", at = @At("HEAD"), cancellable = true) + private void preventTimeTicking(CallbackInfo ci) { + if (DimThread.MANAGER.isActive(getServer()) && !onMainThread) { + timeTickedOnWorldThread = true; + ci.cancel(); + } + } + + @Override + public void dimthread_tickTime() { + if (timeTickedOnWorldThread) { + onMainThread = true; + tickTime(); + onMainThread = false; + timeTickedOnWorldThread = false; + } + } +} diff --git a/src/main/java/wearblackallday/dimthread/util/ServerWorldAccessor.java b/src/main/java/wearblackallday/dimthread/util/ServerWorldAccessor.java new file mode 100644 index 0000000..50fedfc --- /dev/null +++ b/src/main/java/wearblackallday/dimthread/util/ServerWorldAccessor.java @@ -0,0 +1,8 @@ +package wearblackallday.dimthread.util; + +/** + * Create this calass wo that we can call tickTime from ServerWorldMixin + */ +public interface ServerWorldAccessor { + void dimthread_tickTime(); +} diff --git a/src/main/resources/dimthread.mixins.json b/src/main/resources/dimthread.mixins.json index 60c38f2..8ec27e2 100644 --- a/src/main/resources/dimthread.mixins.json +++ b/src/main/resources/dimthread.mixins.json @@ -8,6 +8,7 @@ "MinecraftServerMixin", "RedstoneWireBlockMixin", "ServerChunkManagerMixin", + "ServerWorldMixin", "WorldMixin" ], "client": [