├── .gitignore
├── LICENSE
├── README.MD
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── settings.gradle
└── src
└── main
├── java
└── me
│ └── contaria
│ └── seedqueue
│ ├── SeedQueue.java
│ ├── SeedQueueConfig.java
│ ├── SeedQueueEntry.java
│ ├── SeedQueueException.java
│ ├── SeedQueueExecutorWrapper.java
│ ├── SeedQueueMixinConfigPlugin.java
│ ├── SeedQueueThread.java
│ ├── compat
│ ├── FastResetCompat.java
│ ├── ModCompat.java
│ ├── SeedQueuePreviewFrameBuffer.java
│ ├── SeedQueuePreviewProperties.java
│ ├── SeedQueueSettingsCache.java
│ ├── SodiumCompat.java
│ ├── StandardSettingsCompat.java
│ ├── StateOutputCompat.java
│ └── WorldPreviewCompat.java
│ ├── customization
│ ├── AnimatedTexture.java
│ ├── Layout.java
│ └── LockTexture.java
│ ├── debug
│ ├── SeedQueueProfiler.java
│ ├── SeedQueueSystemInfo.java
│ └── SeedQueueWatchdog.java
│ ├── gui
│ ├── SeedQueueCrashToast.java
│ ├── config
│ │ ├── SeedQueueKeyButtonWidget.java
│ │ ├── SeedQueueKeybindingsListWidget.java
│ │ ├── SeedQueueKeybindingsScreen.java
│ │ └── SeedQueueWindowSizeWidget.java
│ └── wall
│ │ ├── SeedQueueBenchmarkToast.java
│ │ ├── SeedQueuePreview.java
│ │ └── SeedQueueWallScreen.java
│ ├── interfaces
│ ├── SQMinecraftServer.java
│ ├── SQSoundManager.java
│ ├── SQSoundSystem.java
│ ├── SQWorldGenerationProgressLogger.java
│ ├── SQWorldGenerationProgressTracker.java
│ ├── sodium
│ │ └── SQChunkBuilder$WorkerRunnable.java
│ └── worldpreview
│ │ └── SQWorldRenderer.java
│ ├── keybindings
│ ├── SeedQueueKeyBindings.java
│ └── SeedQueueMultiKeyBinding.java
│ ├── mixin
│ ├── accessor
│ │ ├── CameraAccessor.java
│ │ ├── DebugHudAccessor.java
│ │ ├── EntityAccessor.java
│ │ ├── MinecraftClientAccessor.java
│ │ ├── MinecraftServerAccessor.java
│ │ ├── PlayerEntityAccessor.java
│ │ ├── UtilAccessor.java
│ │ ├── WorldGenerationProgressTrackerAccessor.java
│ │ └── WorldRendererAccessor.java
│ ├── client
│ │ ├── CreateWorldScreenMixin.java
│ │ ├── MinecraftClientMixin.java
│ │ ├── WorldGenerationProgressLoggerMixin.java
│ │ ├── WorldGenerationProgressTrackerMixin.java
│ │ ├── debug
│ │ │ └── DebugHudMixin.java
│ │ ├── levellist
│ │ │ └── LevelStorageMixin.java
│ │ ├── profiling
│ │ │ └── WorldRendererMixin.java
│ │ ├── render
│ │ │ ├── LevelLoadingScreenMixin.java
│ │ │ └── MinecraftClientMixin.java
│ │ └── sounds
│ │ │ ├── SoundManagerMixin.java
│ │ │ └── SoundSystemMixin.java
│ ├── compat
│ │ ├── atum
│ │ │ ├── AttemptTrackerMixin.java
│ │ │ ├── AtumMixin.java
│ │ │ ├── CreateWorldScreenMixin.java
│ │ │ └── KeyboardMixin.java
│ │ ├── sodium
│ │ │ ├── ChunkBuilder$WorkerRunnableMixin.java
│ │ │ ├── ChunkBuilder$WorkerRunnableMixin2.java
│ │ │ ├── ChunkBuilderMixin.java
│ │ │ ├── ChunkRenderManagerMixin.java
│ │ │ ├── ChunkRenderShaderBackendMixin.java
│ │ │ └── profiling
│ │ │ │ ├── ChunkBuilderMixin.java
│ │ │ │ ├── ChunkRenderCacheLocalMixin.java
│ │ │ │ ├── ChunkRenderManagerMixin.java
│ │ │ │ ├── MultidrawChunkRenderBackendMixin.java
│ │ │ │ └── SodiumWorldRendererMixin.java
│ │ ├── standardsettings
│ │ │ └── MinecraftClientMixin.java
│ │ └── worldpreview
│ │ │ ├── ThreadedAnvilChunkStorageMixin.java
│ │ │ ├── WorldPreviewMixin.java
│ │ │ └── render
│ │ │ ├── ClientWorldMixin.java
│ │ │ ├── InGameHudMixin.java
│ │ │ ├── WindowMixin.java
│ │ │ └── WorldRendererMixin.java
│ └── server
│ │ ├── IntegratedServerMixin.java
│ │ ├── MinecraftServerMixin.java
│ │ ├── optimization
│ │ └── ThreadedAnvilChunkStorageMixin.java
│ │ ├── parity
│ │ └── EntityMixin.java
│ │ └── synchronization
│ │ ├── BiomeMixin.java
│ │ ├── DirectionTransformationMixin.java
│ │ ├── ScheduledTickMixin.java
│ │ └── WeightedBlockStateProviderMixin.java
│ └── sounds
│ └── SeedQueueSounds.java
└── resources
├── assets
└── seedqueue
│ ├── icon.png
│ ├── lang
│ ├── cs_cz.json
│ ├── en_us.json
│ ├── es_es.json
│ ├── he_il.json
│ ├── pl_pl.json
│ └── zh_cn.json
│ ├── sounds.json
│ ├── sounds
│ ├── lock_instance.ogg
│ └── reset_instance.ogg
│ └── textures
│ └── gui
│ └── wall
│ └── lock.png
├── fabric.mod.json
└── seedqueue.mixins.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # gradle
2 |
3 | .gradle/
4 | build/
5 | out/
6 | classes/
7 |
8 | # eclipse
9 |
10 | *.launch
11 |
12 | # idea
13 |
14 | .idea/
15 | *.iml
16 | *.ipr
17 | *.iws
18 |
19 | # vscode
20 |
21 | .settings/
22 | .vscode/
23 | bin/
24 | .classpath
25 | .project
26 |
27 | # macos
28 |
29 | *.DS_Store
30 |
31 | # fabric
32 |
33 | run/
34 | libs/
35 |
36 | # java
37 |
38 | hs_err_*.log
39 | replay_*.log
40 | *.hprof
41 | *.jfr
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 contaria
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # SeedQueue
2 | ## Description
3 | SeedQueue is a mod that is meant to replace multi-instancing. Instead of having multiple Minecrafts generating worlds open at the same time, it does it all in just one Minecraft instance as it has a built-in wall screen that can be configured through the SpeedrunAPI menu and further customized through resource packs. This greatly improves performance, especially for lower end hardware, and is also aimed to make speedrunning more accessible.
4 | ## Dependencies
5 | To use [SeedQueue](https://github.com/KingContaria/seedqueue/releases/latest) you will also need Atum and SpeedrunAPI, you can get them from [here](https://mods.tildejustin.dev/).
6 | ## Latest Release
7 | Get the latest release of SeedQueue from [here](https://github.com/KingContaria/seedqueue/releases/latest).
8 | ## Tutorial to get started with SeedQueue
9 | If you are new and want to get started with SeedQueue watch this [tutorial](https://www.youtube.com/watch?v=fGu2MYZxh_c).
10 | ## SeedQueue Wiki
11 | You can learn more about the config settings for SeedQueue and get guidance for customization of your Wall Screen on the [SeedQueue Wiki](https://github.com/KingContaria/seedqueue/wiki).
12 | ## Discord Server
13 | If you are looking forward to getting in touch with the SeedQueue Community, join the SeedQueue discord server by clicking [here](https://discord.gg/9P6PJkHCdU).
14 |
15 | ## Acknowledgements
16 |
17 |
18 |
19 | I use the [YourKit Java Profiler](https://www.yourkit.com/java/profiler/) for profiling when developing SeedQueue.
20 | Thanks to YourKit for providing a free license to their product.
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'fabric-loom' version '1.7-SNAPSHOT'
3 | id 'maven-publish'
4 | }
5 |
6 | version = project.mod_version + "+" + project.target_version
7 | group = project.maven_group
8 |
9 | base {
10 | archivesName = project.archives_base_name
11 | }
12 |
13 | repositories {
14 | maven { url 'https://jitpack.io' }
15 | }
16 |
17 | dependencies {
18 | // To change the versions see the gradle.properties file
19 | minecraft "com.mojang:minecraft:${project.minecraft_version}"
20 | mappings "dev.tildejustin:yarn:${project.yarn_mappings}:v2"
21 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
22 |
23 | // SpeedrunAPI
24 | // check for the latest versions at https://jitpack.io/#kingcontaria/speedrunapi
25 | modImplementation "com.github.KingContaria:SpeedrunAPI:8497ae7479"
26 | annotationProcessor "com.github.bawnorton.mixinsquared:mixinsquared-fabric:0.1.1"
27 |
28 | // check for the latest versions at https://jitpack.io/#kingcontaria/atum-rewrite
29 | modImplementation ("com.github.KingContaria:atum-rewrite:d0ac2178f8") {
30 | transitive = false
31 | }
32 |
33 | // check for the latest versions at https://jitpack.io/#kingcontaria/mcsr-worldpreview-1.16.1
34 | modCompileOnly ("com.github.KingContaria:mcsr-worldpreview-1.16.1:649212d145") {
35 | transitive = false
36 | }
37 |
38 | // check for the latest versions at https://jitpack.io/#Minecraft-Java-Edition-Speedrunning/sodium
39 | modCompileOnly "com.github.Minecraft-Java-Edition-Speedrunning:sodium:d488d3a782"
40 |
41 | // check for the latest versions at https://jitpack.io/#kingcontaria/fastreset
42 | modCompileOnly ("com.github.KingContaria:fastreset:7da3236482") {
43 | transitive = false
44 | }
45 |
46 | // check for the latest versions at https://jitpack.io/#kingcontaria/standardsettings
47 | modCompileOnly ("com.github.KingContaria:standardsettings:a22107e94e") {
48 | transitive = false
49 | }
50 |
51 | // check for latest versions at https://jitpack.io/#dev.tildejustin/state-output
52 | modCompileOnly "dev.tildejustin.state-output:state-output-common:v1.2.0"
53 | }
54 |
55 | processResources {
56 | inputs.property "version", version
57 |
58 | filesMatching("fabric.mod.json") {
59 | expand "version": version
60 | }
61 | }
62 |
63 | tasks.withType(JavaCompile).configureEach {
64 | it.options.encoding = "UTF-8"
65 |
66 | def targetVersion = 8
67 | if (JavaVersion.current().isJava9Compatible()) {
68 | it.options.release = targetVersion
69 | }
70 | }
71 |
72 | java {
73 | sourceCompatibility = JavaVersion.VERSION_1_8
74 | targetCompatibility = JavaVersion.VERSION_1_8
75 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
76 | // if it is present.
77 | // If you remove this line, sources will not be generated.
78 | withSourcesJar()
79 | }
80 |
81 | jar {
82 | from("LICENSE") {
83 | rename { "${it}_${project.base.archivesName.get()}"}
84 | }
85 | }
86 |
87 | // configure the maven publication
88 | publishing {
89 | publications {
90 | mavenJava(MavenPublication) {
91 | from components.java
92 | }
93 | }
94 |
95 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
96 | repositories {
97 | // Add repositories to publish to here.
98 | // Notice: This block does NOT have the same function as the block in the top level.
99 | // The repositories here will be used for publishing your artifact, not for
100 | // retrieving dependencies.
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Done to increase the memory available to gradle.
2 | org.gradle.jvmargs=-Xmx1G
3 | org.gradle.parallel=true
4 |
5 | # Fabric Properties
6 | # check these on https://fabricmc.net/develop
7 | minecraft_version=1.16.1
8 | yarn_mappings=1.16.1-build.24
9 | loader_version=0.16.10
10 |
11 | # Mod Properties
12 | mod_version=1.4
13 | target_version=1.16.1
14 | maven_group=me.contaria
15 | archives_base_name=seedqueue
16 |
17 | # Dependencies
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KingContaria/seedqueue/12d5a8b6d91b7c7018828e5c5660eeaedcf4b422/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
90 | ' "$PWD" ) || exit
91 |
92 | # Use the maximum available, or set MAX_FD != -1 to use that value.
93 | MAX_FD=maximum
94 |
95 | warn () {
96 | echo "$*"
97 | } >&2
98 |
99 | die () {
100 | echo
101 | echo "$*"
102 | echo
103 | exit 1
104 | } >&2
105 |
106 | # OS specific support (must be 'true' or 'false').
107 | cygwin=false
108 | msys=false
109 | darwin=false
110 | nonstop=false
111 | case "$( uname )" in #(
112 | CYGWIN* ) cygwin=true ;; #(
113 | Darwin* ) darwin=true ;; #(
114 | MSYS* | MINGW* ) msys=true ;; #(
115 | NONSTOP* ) nonstop=true ;;
116 | esac
117 |
118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
119 |
120 |
121 | # Determine the Java command to use to start the JVM.
122 | if [ -n "$JAVA_HOME" ] ; then
123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
124 | # IBM's JDK on AIX uses strange locations for the executables
125 | JAVACMD=$JAVA_HOME/jre/sh/java
126 | else
127 | JAVACMD=$JAVA_HOME/bin/java
128 | fi
129 | if [ ! -x "$JAVACMD" ] ; then
130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
131 |
132 | Please set the JAVA_HOME variable in your environment to match the
133 | location of your Java installation."
134 | fi
135 | else
136 | JAVACMD=java
137 | if ! command -v java >/dev/null 2>&1
138 | then
139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
140 |
141 | Please set the JAVA_HOME variable in your environment to match the
142 | location of your Java installation."
143 | fi
144 | fi
145 |
146 | # Increase the maximum file descriptors if we can.
147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
148 | case $MAX_FD in #(
149 | max*)
150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
151 | # shellcheck disable=SC2039,SC3045
152 | MAX_FD=$( ulimit -H -n ) ||
153 | warn "Could not query maximum file descriptor limit"
154 | esac
155 | case $MAX_FD in #(
156 | '' | soft) :;; #(
157 | *)
158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
159 | # shellcheck disable=SC2039,SC3045
160 | ulimit -n "$MAX_FD" ||
161 | warn "Could not set maximum file descriptor limit to $MAX_FD"
162 | esac
163 | fi
164 |
165 | # Collect all arguments for the java command, stacking in reverse order:
166 | # * args from the command line
167 | # * the main class name
168 | # * -classpath
169 | # * -D...appname settings
170 | # * --module-path (only if needed)
171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
172 |
173 | # For Cygwin or MSYS, switch paths to Windows format before running java
174 | if "$cygwin" || "$msys" ; then
175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
177 |
178 | JAVACMD=$( cygpath --unix "$JAVACMD" )
179 |
180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
181 | for arg do
182 | if
183 | case $arg in #(
184 | -*) false ;; # don't mess with options #(
185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
186 | [ -e "$t" ] ;; #(
187 | *) false ;;
188 | esac
189 | then
190 | arg=$( cygpath --path --ignore --mixed "$arg" )
191 | fi
192 | # Roll the args list around exactly as many times as the number of
193 | # args, so each arg winds up back in the position where it started, but
194 | # possibly modified.
195 | #
196 | # NB: a `for` loop captures its iteration list before it begins, so
197 | # changing the positional parameters here affects neither the number of
198 | # iterations, nor the values presented in `arg`.
199 | shift # remove old arg
200 | set -- "$@" "$arg" # push replacement arg
201 | done
202 | fi
203 |
204 |
205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
207 |
208 | # Collect all arguments for the java command:
209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
210 | # and any embedded shellness will be escaped.
211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
212 | # treated as '${Hostname}' itself on the command line.
213 |
214 | set -- \
215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
216 | -classpath "$CLASSPATH" \
217 | org.gradle.wrapper.GradleWrapperMain \
218 | "$@"
219 |
220 | # Stop when "xargs" is not available.
221 | if ! command -v xargs >/dev/null 2>&1
222 | then
223 | die "xargs is not available"
224 | fi
225 |
226 | # Use "xargs" to parse quoted args.
227 | #
228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
229 | #
230 | # In Bash we could simply go:
231 | #
232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
233 | # set -- "${ARGS[@]}" "$@"
234 | #
235 | # but POSIX shell has neither arrays nor command substitution, so instead we
236 | # post-process each arg (as a line of input to sed) to backslash-escape any
237 | # character that might be a shell metacharacter, then use eval to reverse
238 | # that process (while maintaining the separation between arguments), and wrap
239 | # the whole thing up as a single "set" statement.
240 | #
241 | # This will of course break if any of these variables contains a newline or
242 | # an unmatched quote.
243 | #
244 |
245 | eval "set -- $(
246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
247 | xargs -n1 |
248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
249 | tr '\n' ' '
250 | )" '"$@"'
251 |
252 | exec "$JAVACMD" "$@"
253 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk17
3 | before_install:
4 | - sdk install java 17.0.1-open
5 | - sdk use java 17.0.1-open
6 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | maven {
4 | name = 'Fabric'
5 | url = 'https://maven.fabricmc.net/'
6 | }
7 | mavenCentral()
8 | gradlePluginPortal()
9 | }
10 | }
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/SeedQueueException.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue;
2 |
3 | /**
4 | * Exception thrown when something goes wrong on the {@link SeedQueueThread}.
5 | */
6 | public class SeedQueueException extends RuntimeException {
7 | public SeedQueueException(String message) {
8 | super(message);
9 | }
10 |
11 | public SeedQueueException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/SeedQueueExecutorWrapper.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue;
2 |
3 | import me.contaria.seedqueue.mixin.accessor.UtilAccessor;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.util.concurrent.Executor;
7 | import java.util.concurrent.ExecutorService;
8 | import java.util.concurrent.ForkJoinPool;
9 | import java.util.concurrent.ForkJoinWorkerThread;
10 | import java.util.concurrent.atomic.AtomicInteger;
11 |
12 | /**
13 | * Wraps the server executor to allow for using seperate executors while a server is generating in queue.
14 | * This allows for modifying thread priorities and parallelism of executors used while in queue to help manage performance.
15 | *
16 | * The backing {@link ExecutorService}'s are created lazily and should be shut down after a SeedQueue session.
17 | * This should happen AFTER all servers have been shut down!
18 | *
19 | * @see SeedQueueConfig#backgroundExecutorThreadPriority
20 | * @see SeedQueueConfig#backgroundExecutorThreads
21 | * @see SeedQueueConfig#wallExecutorThreadPriority
22 | * @see SeedQueueConfig#wallExecutorThreads
23 | */
24 | public class SeedQueueExecutorWrapper implements Executor {
25 | /**
26 | * Executor used by servers while they are in queue.
27 | * Redirects to the backing executors depending on the current state.
28 | */
29 | public static final Executor SEEDQUEUE_EXECUTOR = command -> getSeedqueueExecutor().execute(command);
30 |
31 | private static ExecutorService SEEDQUEUE_BACKGROUND_EXECUTOR;
32 | private static ExecutorService SEEDQUEUE_WALL_EXECUTOR;
33 |
34 | private final Executor originalExecutor;
35 | private Executor executor;
36 |
37 | public SeedQueueExecutorWrapper(Executor originalExecutor) {
38 | this.executor = this.originalExecutor = originalExecutor;
39 | }
40 |
41 | @Override
42 | public void execute(@NotNull Runnable command) {
43 | this.executor.execute(command);
44 | }
45 |
46 | public void setExecutor(Executor executor) {
47 | this.executor = executor;
48 | }
49 |
50 | public void resetExecutor() {
51 | this.setExecutor(this.originalExecutor);
52 | }
53 |
54 | private static Executor getSeedqueueExecutor() {
55 | // if Max Generating Seeds is set to 0 while not on wall,
56 | // this will ensure the background executor is never created
57 | if (SeedQueue.isOnWall() || SeedQueue.config.maxConcurrently == 0) {
58 | return getOrCreateWallExecutor();
59 | }
60 | return getOrCreateBackgroundExecutor();
61 | }
62 |
63 | private synchronized static Executor getOrCreateBackgroundExecutor() {
64 | if (SEEDQUEUE_BACKGROUND_EXECUTOR == null) {
65 | SEEDQUEUE_BACKGROUND_EXECUTOR = createExecutor("SeedQueue", SeedQueue.config.getBackgroundExecutorThreads(), SeedQueue.config.backgroundExecutorThreadPriority);
66 | }
67 | return SEEDQUEUE_BACKGROUND_EXECUTOR;
68 | }
69 |
70 | private synchronized static Executor getOrCreateWallExecutor() {
71 | if (SEEDQUEUE_WALL_EXECUTOR == null) {
72 | SEEDQUEUE_WALL_EXECUTOR = createExecutor("SeedQueue Wall", SeedQueue.config.getWallExecutorThreads(), SeedQueue.config.wallExecutorThreadPriority);
73 | }
74 | return SEEDQUEUE_WALL_EXECUTOR;
75 | }
76 |
77 | // see Util#createWorker
78 | private static ExecutorService createExecutor(String name, int threads, int priority) {
79 | AtomicInteger threadCount = new AtomicInteger();
80 | return new ForkJoinPool(threads, pool -> {
81 | ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
82 | thread.setName("Worker-" + name + "-" + threadCount.getAndIncrement());
83 | thread.setPriority(priority);
84 | return thread;
85 | }, UtilAccessor::seedQueue$uncaughtExceptionHandler, true);
86 | }
87 |
88 | /**
89 | * Shuts down and removes the SeedQueue specific {@link ExecutorService}s.
90 | */
91 | public synchronized static void shutdownExecutors() {
92 | if (SEEDQUEUE_BACKGROUND_EXECUTOR != null) {
93 | UtilAccessor.seedQueue$attemptShutdown(SEEDQUEUE_BACKGROUND_EXECUTOR);
94 | SEEDQUEUE_BACKGROUND_EXECUTOR = null;
95 | }
96 | if (SEEDQUEUE_WALL_EXECUTOR != null) {
97 | UtilAccessor.seedQueue$attemptShutdown(SEEDQUEUE_WALL_EXECUTOR);
98 | SEEDQUEUE_WALL_EXECUTOR = null;
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/SeedQueueMixinConfigPlugin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue;
2 |
3 | import com.bawnorton.mixinsquared.api.MixinCanceller;
4 | import me.contaria.speedrunapi.mixin_plugin.SpeedrunMixinConfigPlugin;
5 | import net.fabricmc.loader.api.FabricLoader;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * SeedQueues Mixin Config Plugin, extends SpeedrunAPI's plugin to inherit its functionality.
11 | * Only used for compatibility with Sodium Mac, will be redundant once Sodium is updated to not need the seperate mac version anymore.
12 | */
13 | public class SeedQueueMixinConfigPlugin extends SpeedrunMixinConfigPlugin implements MixinCanceller {
14 | private static final boolean MAC_SODIUM = FabricLoader.getInstance().isModLoaded("sodiummac");
15 |
16 | @Override
17 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
18 | // Sodium Mac "Compat"
19 | // Create RenderCache Async doesn't apply on Sodium Mac
20 | if (MAC_SODIUM) {
21 | if (mixinClassName.startsWith(this.mixinPackage + ".compat.sodium.profiling")) {
22 | return false;
23 | }
24 | if (mixinClassName.equals(this.mixinPackage + ".compat.sodium.ClientChunkManagerMixin") || mixinClassName.equals(this.mixinPackage + ".compat.sodium.ChunkBuilder$WorkerRunnableMixin2")) {
25 | return false;
26 | }
27 | }
28 | return super.shouldApplyMixin(targetClassName, mixinClassName);
29 | }
30 |
31 | @Override
32 | public boolean shouldCancel(List targetClassNames, String mixinClassName) {
33 | // SleepBackgrounds Thread Executor mixin has been observed to hurt performance when SeedQueue is active
34 | // since SeedQueue spawns many more executors
35 | return mixinClassName.equals("com.redlimerl.sleepbackground.mixin.MixinThreadExecutor");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/SeedQueueThread.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue;
2 |
3 | import me.contaria.seedqueue.gui.SeedQueueCrashToast;
4 | import me.contaria.speedrunapi.util.TextUtil;
5 | import me.voidxwalker.autoreset.AtumCreateWorldScreen;
6 | import net.minecraft.client.MinecraftClient;
7 |
8 | import java.util.List;
9 | import java.util.Optional;
10 | import java.util.concurrent.atomic.AtomicBoolean;
11 |
12 | /**
13 | * The thread responsible for launching new server threads and creating and queueing the corresponding {@link SeedQueueEntry}'s.
14 | *
15 | * This thread is to be launched at the start of a SeedQueue session and to be closed by calling {@link SeedQueueThread#stopQueue}.
16 | */
17 | public class SeedQueueThread extends Thread {
18 | public static final Object WORLD_CREATION_LOCK = new Object();
19 |
20 | private final Object lock = new Object();
21 | private final AtomicBoolean pinged = new AtomicBoolean();
22 | private volatile boolean running = true;
23 |
24 | SeedQueueThread() {
25 | super("SeedQueue Thread");
26 | this.setPriority(SeedQueue.config.seedQueueThreadPriority);
27 | }
28 |
29 | @Override
30 | public void run() {
31 | while (this.running) {
32 | try {
33 | // clear pinged state when starting a new check
34 | this.pinged.set(false);
35 | if (SeedQueue.shouldPauseGenerating()) {
36 | this.pauseSeedQueueEntry();
37 | continue;
38 | }
39 | boolean shouldResumeAfterQueueFull = SeedQueue.shouldResumeAfterQueueFull();
40 | if (!SeedQueue.shouldGenerate() || shouldResumeAfterQueueFull) {
41 | boolean shouldResumeGenerating = SeedQueue.shouldResumeGenerating();
42 | if (!shouldResumeGenerating && !SeedQueue.noLockedRemaining() && shouldResumeAfterQueueFull) {
43 | this.pauseSeedQueueEntry();
44 | continue;
45 | }
46 | if (shouldResumeGenerating && this.unpauseSeedQueueEntry()) {
47 | continue;
48 | }
49 | synchronized (this.lock) {
50 | // don't freeze thread if it's been pinged at any point during the check
51 | if (this.pinged.get()) {
52 | continue;
53 | }
54 | this.lock.wait();
55 | }
56 | continue;
57 | }
58 |
59 | if (this.unpauseSeedQueueEntry()) {
60 | continue;
61 | }
62 |
63 | this.createSeedQueueEntry();
64 | } catch (Exception e) {
65 | SeedQueue.LOGGER.error("Shutting down SeedQueue Thread...", e);
66 | SeedQueue.scheduleTaskOnClientThread(() -> MinecraftClient.getInstance().getToastManager().add(new SeedQueueCrashToast(
67 | TextUtil.translatable("seedqueue.menu.crash.title"),
68 | TextUtil.translatable("seedqueue.menu.crash.description", e.getClass().getSimpleName())
69 | )));
70 | this.stopQueue();
71 | }
72 | }
73 | }
74 |
75 | /**
76 | * Tries to find a currently unpaused {@link SeedQueueEntry} and schedules it to be paused.
77 | */
78 | private void pauseSeedQueueEntry() {
79 | // try to pause not locked entries first
80 | Optional entry = SeedQueue.getEntryMatching(e -> !e.isLocked() && e.canPause());
81 | if (!entry.isPresent()) {
82 | entry = SeedQueue.getEntryMatching(SeedQueueEntry::canPause);
83 | }
84 | entry.ifPresent(SeedQueueEntry::schedulePause);
85 | }
86 |
87 | /**
88 | * Tries to find a currently paused {@link SeedQueueEntry} and schedules it to be unpaused.
89 | *
90 | * @return False if there is no entries to unpause.
91 | */
92 | private boolean unpauseSeedQueueEntry() {
93 | List entries = SeedQueue.getEntries();
94 | // try to unpause locked entries first
95 | for (SeedQueueEntry entry : entries) {
96 | if (entry.isLocked() && entry.tryToUnpause()) {
97 | return true;
98 | }
99 | }
100 | for (SeedQueueEntry entry : entries) {
101 | if (entry.tryToUnpause()) {
102 | return true;
103 | }
104 | }
105 | return false;
106 | }
107 |
108 | /**
109 | * Creates a new {@link SeedQueueEntry} and adds it to the queue.
110 | */
111 | private void createSeedQueueEntry() {
112 | synchronized (WORLD_CREATION_LOCK) {
113 | new AtumCreateWorldScreen(null).init(MinecraftClient.getInstance(), 0, 0);
114 | }
115 | }
116 |
117 | public void ping() {
118 | synchronized (this.lock) {
119 | this.pinged.set(true);
120 | this.lock.notify();
121 | }
122 | }
123 |
124 | /**
125 | * Stops this thread.
126 | *
127 | * If this method is called from another thread, {@link SeedQueueThread#ping} has to be called AFTER.
128 | */
129 | public void stopQueue() {
130 | this.running = false;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/compat/FastResetCompat.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.compat;
2 |
3 | import fast_reset.client.interfaces.FRMinecraftServer;
4 | import net.minecraft.server.MinecraftServer;
5 |
6 | class FastResetCompat {
7 |
8 | static void fastReset(MinecraftServer server) {
9 | ((FRMinecraftServer) server).fastReset$fastReset();
10 | }
11 |
12 | static boolean shouldSave(MinecraftServer server) {
13 | return ((FRMinecraftServer) server).fastReset$shouldSave();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/compat/ModCompat.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.compat;
2 |
3 | import me.contaria.standardsettings.StandardSettings;
4 | import net.fabricmc.loader.api.FabricLoader;
5 | import net.minecraft.server.MinecraftServer;
6 |
7 | /**
8 | * Intermediate class that allows safe access to other mods methods/fields from code paths that may be run without the mod present.
9 | * This class provides wrapper methods for compat classes which should only be classloaded if the mod in question is loaded.
10 | */
11 | public class ModCompat {
12 | public static final boolean HAS_FASTRESET = FabricLoader.getInstance().isModLoaded("fast_reset");
13 | public static final boolean HAS_SODIUM = FabricLoader.getInstance().isModLoaded("sodium");
14 | public static final boolean HAS_STANDARDSETTINGS = FabricLoader.getInstance().isModLoaded("standardsettings");
15 | public static final boolean HAS_WORLDPREVIEW = FabricLoader.getInstance().isModLoaded("worldpreview");
16 | public static final boolean HAS_STATEOUTPUT = FabricLoader.getInstance().isModLoaded("state-output");
17 |
18 | public static void fastReset$fastReset(MinecraftServer server) {
19 | if (HAS_FASTRESET) {
20 | FastResetCompat.fastReset(server);
21 | }
22 | }
23 |
24 | public static boolean fastReset$shouldSave(MinecraftServer server) {
25 | if (HAS_FASTRESET) {
26 | return FastResetCompat.shouldSave(server);
27 | }
28 | return false;
29 | }
30 |
31 | public static void stateoutput$setWallState() {
32 | if (HAS_STATEOUTPUT) {
33 | StateOutputCompat.setWallState();
34 | }
35 | }
36 |
37 | public static void sodium$clearShaderCache() {
38 | if (HAS_SODIUM) {
39 | SodiumCompat.clearShaderCache();
40 | }
41 | }
42 |
43 | public static void sodium$clearBuildBufferPool() {
44 | if (HAS_SODIUM) {
45 | SodiumCompat.clearBuildBufferPool();
46 | }
47 | }
48 |
49 | public static void standardsettings$cache() {
50 | if (HAS_STANDARDSETTINGS) {
51 | StandardSettingsCompat.createCache();
52 | }
53 | }
54 |
55 | public static void standardsettings$reset() {
56 | if (HAS_STANDARDSETTINGS) {
57 | StandardSettingsCompat.resetPendingActions();
58 | if (StandardSettings.isEnabled()) {
59 | StandardSettingsCompat.reset();
60 | }
61 | }
62 | }
63 |
64 | public static void standardsettings$loadCache() {
65 | if (HAS_STANDARDSETTINGS) {
66 | StandardSettingsCompat.loadCache();
67 | }
68 | }
69 |
70 | public static boolean worldpreview$inPreview() {
71 | if (HAS_WORLDPREVIEW) {
72 | return WorldPreviewCompat.inPreview();
73 | }
74 | return false;
75 | }
76 |
77 | public static boolean worldpreview$kill(MinecraftServer server) {
78 | if (HAS_WORLDPREVIEW) {
79 | return WorldPreviewCompat.kill(server);
80 | }
81 | return false;
82 | }
83 |
84 | public static void worldpreview$clearFramebufferPool() {
85 | if (HAS_WORLDPREVIEW) {
86 | SeedQueuePreviewFrameBuffer.clearFramebufferPool();
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/compat/SeedQueuePreviewFrameBuffer.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.compat;
2 |
3 | import com.mojang.blaze3d.systems.RenderSystem;
4 | import me.contaria.seedqueue.SeedQueue;
5 | import net.minecraft.client.MinecraftClient;
6 | import net.minecraft.client.gl.Framebuffer;
7 | import net.minecraft.client.render.BufferBuilder;
8 | import net.minecraft.client.render.Tessellator;
9 | import net.minecraft.client.render.VertexFormats;
10 | import net.minecraft.client.render.WorldRenderer;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.Objects;
16 |
17 | /**
18 | * Wrapper for Minecrafts {@link Framebuffer} storing a previews last drawn image.
19 | *
20 | * Stores {@link SeedQueuePreviewFrameBuffer#lastRenderData} as to only redraw the preview if it has changed.
21 | */
22 | public class SeedQueuePreviewFrameBuffer {
23 | private static final List FRAMEBUFFER_POOL = new ArrayList<>();
24 |
25 | private final Framebuffer framebuffer;
26 |
27 | // stores a string unique to the current state of world rendering when writing to the framebuffer
28 | @Nullable
29 | private String lastRenderData;
30 |
31 | public SeedQueuePreviewFrameBuffer() {
32 | if (FRAMEBUFFER_POOL.isEmpty()) {
33 | this.framebuffer = new Framebuffer(
34 | SeedQueue.config.simulatedWindowSize.width(),
35 | SeedQueue.config.simulatedWindowSize.height(),
36 | true,
37 | MinecraftClient.IS_SYSTEM_MAC
38 | );
39 | } else {
40 | this.framebuffer = FRAMEBUFFER_POOL.remove(0);
41 | }
42 | }
43 |
44 | public void beginWrite() {
45 | this.framebuffer.beginWrite(true);
46 | }
47 |
48 | public void endWrite() {
49 | this.framebuffer.endWrite();
50 | }
51 |
52 | public boolean updateRenderData(WorldRenderer worldRenderer) {
53 | return !Objects.equals(this.lastRenderData, this.lastRenderData = worldRenderer.getChunksDebugString() + "\n" + worldRenderer.getEntitiesDebugString());
54 | }
55 |
56 | /**
57 | * Draws the internal {@link Framebuffer} without setting {@link RenderSystem#ortho} and {@link RenderSystem#viewport}.
58 | */
59 | @SuppressWarnings("deprecation")
60 | public void draw(int width, int height) {
61 | RenderSystem.assertThread(RenderSystem::isOnRenderThread);
62 |
63 | RenderSystem.colorMask(true, true, true, false);
64 | RenderSystem.disableDepthTest();
65 | RenderSystem.depthMask(false);
66 | RenderSystem.enableTexture();
67 | RenderSystem.disableLighting();
68 | RenderSystem.disableAlphaTest();
69 | RenderSystem.disableBlend();
70 | RenderSystem.color4f(1.0f, 1.0f, 1.0f, 1.0f);
71 |
72 | this.framebuffer.beginRead();
73 | Tessellator tessellator = RenderSystem.renderThreadTesselator();
74 | BufferBuilder bufferBuilder = tessellator.getBuffer();
75 | bufferBuilder.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
76 | bufferBuilder.vertex(0.0, height, 0.0).texture(0.0f, 0.0f).color(255, 255, 255, 255).next();
77 | bufferBuilder.vertex(width, height, 0.0).texture(1.0f, 0.0f).color(255, 255, 255, 255).next();
78 | bufferBuilder.vertex(width, 0.0, 0.0).texture(1.0f, 1.0f).color(255, 255, 255, 255).next();
79 | bufferBuilder.vertex(0.0, 0.0, 0.0).texture(0.0f, 1.0f).color(255, 255, 255, 255).next();
80 | tessellator.draw();
81 | this.framebuffer.endRead();
82 |
83 | RenderSystem.depthMask(true);
84 | RenderSystem.colorMask(true, true, true, true);
85 | }
86 |
87 | public void discard() {
88 | FRAMEBUFFER_POOL.add(this.framebuffer);
89 | }
90 |
91 | static void clearFramebufferPool() {
92 | for (Framebuffer framebuffer : FRAMEBUFFER_POOL) {
93 | framebuffer.delete();
94 | }
95 | FRAMEBUFFER_POOL.clear();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/compat/SeedQueuePreviewProperties.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.compat;
2 |
3 | import com.mojang.blaze3d.systems.RenderSystem;
4 | import me.contaria.seedqueue.debug.SeedQueueProfiler;
5 | import me.contaria.seedqueue.interfaces.worldpreview.SQWorldRenderer;
6 | import me.contaria.seedqueue.mixin.accessor.CameraAccessor;
7 | import me.contaria.seedqueue.mixin.accessor.PlayerEntityAccessor;
8 | import me.voidxwalker.worldpreview.WorldPreview;
9 | import me.voidxwalker.worldpreview.WorldPreviewProperties;
10 | import net.minecraft.client.MinecraftClient;
11 | import net.minecraft.client.network.ClientPlayerEntity;
12 | import net.minecraft.client.network.ClientPlayerInteractionManager;
13 | import net.minecraft.client.render.Camera;
14 | import net.minecraft.client.render.DiffuseLighting;
15 | import net.minecraft.client.render.entity.PlayerModelPart;
16 | import net.minecraft.client.util.Window;
17 | import net.minecraft.client.util.math.MatrixStack;
18 | import net.minecraft.client.util.math.Vector3f;
19 | import net.minecraft.client.world.ClientWorld;
20 | import net.minecraft.network.Packet;
21 | import net.minecraft.util.math.Matrix4f;
22 |
23 | import java.util.Queue;
24 |
25 | public class SeedQueuePreviewProperties extends WorldPreviewProperties {
26 | public SeedQueuePreviewProperties(ClientWorld world, ClientPlayerEntity player, ClientPlayerInteractionManager interactionManager, Camera camera, Queue> packetQueue) {
27 | super(world, player, interactionManager, camera, packetQueue);
28 | }
29 |
30 | public int getPerspective() {
31 | return this.camera.isThirdPerson() ? ((CameraAccessor) this.camera).seedQueue$isInverseView() ? 2 : 1 : 0;
32 | }
33 |
34 | /**
35 | * Builds chunks for the current {@link WorldPreview#worldRenderer} without rendering the preview.
36 | *
37 | * @see WorldPreviewProperties#render
38 | */
39 | @SuppressWarnings("deprecation")
40 | public void buildChunks() {
41 | MinecraftClient client = MinecraftClient.getInstance();
42 | Window window = client.getWindow();
43 | Camera camera = client.gameRenderer.getCamera();
44 |
45 | SeedQueueProfiler.swap("build_preview");
46 |
47 | RenderSystem.clear(256, MinecraftClient.IS_SYSTEM_MAC);
48 | RenderSystem.loadIdentity();
49 | RenderSystem.ortho(0.0, window.getFramebufferWidth(), window.getFramebufferHeight(), 0.0, 1000.0, 3000.0);
50 | RenderSystem.loadIdentity();
51 | RenderSystem.translatef(0.0F, 0.0F, 0.0F);
52 | DiffuseLighting.disableGuiDepthLighting();
53 |
54 | SeedQueueProfiler.push("matrix");
55 | // see GameRenderer#renderWorld
56 | MatrixStack rotationMatrix = new MatrixStack();
57 | rotationMatrix.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(camera.getPitch()));
58 | rotationMatrix.multiply(Vector3f.POSITIVE_Y.getDegreesQuaternion(camera.getYaw() + 180.0f));
59 |
60 | Matrix4f projectionMatrix = new Matrix4f();
61 | client.gameRenderer.loadProjectionMatrix(projectionMatrix);
62 |
63 | SeedQueueProfiler.swap("camera_update");
64 | camera.update(client.world, client.player, camera.isThirdPerson(), ((CameraAccessor) camera).seedQueue$isInverseView(), 1.0f);
65 | SeedQueueProfiler.swap("build_chunks");
66 | ((SQWorldRenderer) client.worldRenderer).seedQueue$buildChunks(rotationMatrix, camera, projectionMatrix);
67 | SeedQueueProfiler.pop();
68 |
69 | RenderSystem.clear(256, MinecraftClient.IS_SYSTEM_MAC);
70 | RenderSystem.matrixMode(5889);
71 | RenderSystem.loadIdentity();
72 | RenderSystem.ortho(0.0D, (double) window.getFramebufferWidth() / window.getScaleFactor(), (double) window.getFramebufferHeight() / window.getScaleFactor(), 0.0D, 1000.0D, 3000.0D);
73 | RenderSystem.matrixMode(5888);
74 | RenderSystem.loadIdentity();
75 | RenderSystem.translatef(0.0F, 0.0F, -2000.0F);
76 | DiffuseLighting.enableGuiDepthLighting();
77 | RenderSystem.defaultAlphaFunc();
78 | RenderSystem.clear(256, MinecraftClient.IS_SYSTEM_MAC);
79 | }
80 |
81 | public void loadPlayerModelParts() {
82 | // see WorldPreview#configure
83 | int playerModelPartsBitMask = 0;
84 | for (PlayerModelPart playerModelPart : MinecraftClient.getInstance().options.getEnabledPlayerModelParts()) {
85 | playerModelPartsBitMask |= playerModelPart.getBitFlag();
86 | }
87 | this.player.getDataTracker().set(PlayerEntityAccessor.seedQueue$getPLAYER_MODEL_PARTS(), (byte) playerModelPartsBitMask);
88 | }
89 |
90 | public void load() {
91 | WorldPreview.set(this.world, this.player, this.interactionManager, this.camera, this.packetQueue);
92 | }
93 |
94 | @Override
95 | protected int getDataLimit() {
96 | // Don't allow Unlimited (=100) on wall
97 | return Math.min(99, super.getDataLimit());
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/compat/SeedQueueSettingsCache.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.compat;
2 |
3 | import me.contaria.seedqueue.SeedQueue;
4 | import me.contaria.seedqueue.SeedQueueEntry;
5 | import me.contaria.seedqueue.mixin.accessor.PlayerEntityAccessor;
6 | import me.contaria.standardsettings.StandardSettingsCache;
7 | import me.contaria.standardsettings.options.PlayerModelPartStandardSetting;
8 | import me.contaria.standardsettings.options.StandardSetting;
9 | import net.minecraft.client.network.ClientPlayerEntity;
10 |
11 | import java.util.*;
12 | import java.util.stream.Collectors;
13 |
14 | /**
15 | * Saves settings when a preview is first rendered to load them when the corresponding {@link SeedQueueEntry} is entered.
16 | *
17 | * This is to achieve parity with multi instance settings changing.
18 | * If settings wouldn't get cached it would allow people to view preview A, then enter preview B,
19 | * change settings (for example the piechart) to values best suited for preview A, leave preview B and then enter preview A with optimal settings.
20 | */
21 | public class SeedQueueSettingsCache extends StandardSettingsCache {
22 | // a set of all setting id's that affect preview rendering on wall
23 | // while language and forceUnicodeFont also affect previews, they require reloading of some resources which is not feasible
24 | // does not include perspective since it is special cased
25 | // see SeedQueuePreviewProperties#getPerspective
26 | private static final Set PREVIEW_SETTINGS = new HashSet<>(Arrays.asList(
27 | "biomeBlendRadius",
28 | "graphicsMode",
29 | "renderDistance",
30 | "ao",
31 | "guiScale",
32 | "attackIndicator",
33 | "gamma",
34 | "renderClouds",
35 | "particles",
36 | "mipmapLevels",
37 | "entityShadows",
38 | "entityDistanceScaling",
39 | "textBackgroundOpacity",
40 | "backgroundForChatOnly",
41 | "hitboxes",
42 | "chunkborders",
43 | "f1"
44 | ));
45 |
46 | // ignored settings dont get saved in seedqueue settings caches
47 | // fullscreen is ignored since it can also be changed by macros at any point and is quite annoying when it is changed during a session
48 | private static final Set IGNORED_SETTINGS = new HashSet<>(Collections.singletonList(
49 | "fullscreen"
50 | ));
51 |
52 | private final Set> previewSettings;
53 |
54 | private SeedQueueSettingsCache() {
55 | super(null);
56 |
57 | this.cache.removeIf(entry -> IGNORED_SETTINGS.contains(entry.setting.getID()));
58 |
59 | this.previewSettings = new HashSet<>(PREVIEW_SETTINGS.size());
60 | for (Entry> entry : this.cache) {
61 | if (PREVIEW_SETTINGS.contains(entry.setting.getID())) {
62 | this.previewSettings.add(entry);
63 | }
64 | }
65 | }
66 |
67 | /**
68 | * Loads only settings that affect preview rendering.
69 | *
70 | * @see SeedQueueSettingsCache#PREVIEW_SETTINGS
71 | */
72 | public void loadPreview() {
73 | this.previewSettings.forEach(Entry::load);
74 | }
75 |
76 | /**
77 | * Loads player model part settings onto the given {@link ClientPlayerEntity}.
78 | */
79 | public void loadPlayerModelParts(ClientPlayerEntity player) {
80 | int playerModelPartsBitMask = 0;
81 | for (Entry> entry : this.cache) {
82 | if (entry.setting instanceof PlayerModelPartStandardSetting && Boolean.TRUE.equals(entry.value)) {
83 | playerModelPartsBitMask |= ((PlayerModelPartStandardSetting) entry.setting).playerModelPart.getBitFlag();
84 | }
85 | }
86 | player.getDataTracker().set(PlayerEntityAccessor.seedQueue$getPLAYER_MODEL_PARTS(), (byte) playerModelPartsBitMask);
87 | }
88 |
89 | /**
90 | * @param option The options ID according to {@link StandardSetting#getID}.
91 | * @return The cached value for the given option.
92 | */
93 | public Object getValue(String option) {
94 | for (Entry> entry : this.cache) {
95 | if (option.equals(entry.setting.getID())) {
96 | return entry.value;
97 | }
98 | }
99 | return null;
100 | }
101 |
102 | /**
103 | * @return True if the cached settings match the current settings.
104 | */
105 | public boolean isCurrentSettings() {
106 | for (Entry> entry : this.cache) {
107 | if (!Objects.equals(entry.value, entry.setting.getOption())) {
108 | return false;
109 | }
110 | }
111 | return true;
112 | }
113 |
114 | /**
115 | * Searches all {@link SeedQueueEntry}s and returns the first {@link SeedQueueSettingsCache} matching the current settings or creates a new cache if it doesn't find one.
116 | *
117 | * @return A {@link SeedQueueSettingsCache} matching the current settings.
118 | * @see SeedQueueSettingsCache#isCurrentSettings
119 | */
120 | public static SeedQueueSettingsCache create() {
121 | for (SeedQueueSettingsCache settingsCache : SeedQueue.getEntries().stream().map(SeedQueueEntry::getSettingsCache).filter(Objects::nonNull).collect(Collectors.toSet())) {
122 | if (settingsCache.isCurrentSettings()) {
123 | return settingsCache;
124 | }
125 | }
126 | return new SeedQueueSettingsCache();
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/compat/SodiumCompat.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.compat;
2 |
3 | import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers;
4 | import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkFogMode;
5 | import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkProgram;
6 |
7 | import java.util.*;
8 |
9 | public class SodiumCompat {
10 | public static final Map WALL_SHADER_CACHE = new EnumMap<>(ChunkFogMode.class);
11 | public static final List WALL_BUILD_BUFFERS_POOL = Collections.synchronizedList(new ArrayList<>());
12 |
13 | static void clearShaderCache() {
14 | for (ChunkProgram program : WALL_SHADER_CACHE.values()) {
15 | program.delete();
16 | }
17 | WALL_SHADER_CACHE.clear();
18 | }
19 |
20 | static void clearBuildBufferPool() {
21 | WALL_BUILD_BUFFERS_POOL.clear();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/compat/StandardSettingsCompat.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.compat;
2 |
3 | import me.contaria.standardsettings.StandardSettings;
4 |
5 | class StandardSettingsCompat {
6 |
7 | static void reset() {
8 | StandardSettings.reset();
9 | }
10 |
11 | static void createCache() {
12 | StandardSettings.createCache();
13 | }
14 |
15 | static void resetPendingActions() {
16 | StandardSettings.resetPendingActions();
17 | }
18 |
19 | static void loadCache() {
20 | StandardSettings.loadCache(StandardSettings.lastWorld);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/compat/StateOutputCompat.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.compat;
2 |
3 | import dev.tildejustin.stateoutput.State;
4 | import dev.tildejustin.stateoutput.StateOutputHelper;
5 |
6 | public class StateOutputCompat {
7 | static void setWallState() {
8 | StateOutputHelper.outputState(State.WALL);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/compat/WorldPreviewCompat.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.compat;
2 |
3 | import me.voidxwalker.worldpreview.WorldPreview;
4 | import me.voidxwalker.worldpreview.interfaces.WPMinecraftServer;
5 | import net.minecraft.server.MinecraftServer;
6 |
7 | public class WorldPreviewCompat {
8 |
9 | static boolean inPreview() {
10 | return WorldPreview.inPreview();
11 | }
12 |
13 | static boolean kill(MinecraftServer server) {
14 | return ((WPMinecraftServer) server).worldpreview$kill();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/customization/AnimatedTexture.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.customization;
2 |
3 | import me.contaria.seedqueue.SeedQueue;
4 | import net.minecraft.client.MinecraftClient;
5 | import net.minecraft.client.resource.metadata.AnimationResourceMetadata;
6 | import net.minecraft.util.Identifier;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | import java.io.IOException;
10 |
11 | public class AnimatedTexture {
12 | private final Identifier id;
13 | @Nullable
14 | protected final AnimationResourceMetadata animation;
15 |
16 | protected AnimatedTexture(Identifier id) {
17 | this.id = id;
18 | AnimationResourceMetadata animation = null;
19 | try {
20 | animation = MinecraftClient.getInstance().getResourceManager().getResource(id).getMetadata(AnimationResourceMetadata.READER);
21 | } catch (IOException e) {
22 | SeedQueue.LOGGER.warn("Failed to read animation data for {}!", id, e);
23 | }
24 | this.animation = animation;
25 | }
26 |
27 | public Identifier getId() {
28 | return this.id;
29 | }
30 |
31 | public int getFrameIndex(int tick) {
32 | // does not currently support setting frametime for individual frames
33 | // see AnimationFrameResourceMetadata#usesDefaultFrameTime
34 | return this.animation != null ? this.animation.getFrameIndex((tick / this.animation.getDefaultFrameTime()) % this.animation.getFrameCount()) : 0;
35 | }
36 |
37 | public int getIndividualFrameCount() {
38 | return this.animation != null ? this.animation.getFrameIndexSet().size() : 1;
39 | }
40 |
41 | public static @Nullable AnimatedTexture of(Identifier id) {
42 | if (MinecraftClient.getInstance().getResourceManager().containsResource(id)) {
43 | return new AnimatedTexture(id);
44 | }
45 | return null;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/customization/LockTexture.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.customization;
2 |
3 | import me.contaria.seedqueue.SeedQueue;
4 | import me.contaria.speedrunapi.util.IdentifierUtil;
5 | import net.minecraft.client.MinecraftClient;
6 | import net.minecraft.client.texture.NativeImage;
7 | import net.minecraft.util.Identifier;
8 |
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | public class LockTexture extends AnimatedTexture {
14 | private final int width;
15 | private final int height;
16 |
17 | public LockTexture(Identifier id) throws IOException {
18 | super(id);
19 | try (NativeImage image = NativeImage.read(MinecraftClient.getInstance().getResourceManager().getResource(id).getInputStream())) {
20 | this.width = image.getWidth();
21 | this.height = image.getHeight() / (this.animation != null ? this.animation.getFrameIndexSet().size() : 1);
22 | }
23 | }
24 |
25 | public double getAspectRatio() {
26 | return (double) this.width / this.height;
27 | }
28 |
29 | public static List createLockTextures() {
30 | List lockTextures = new ArrayList<>();
31 | Identifier lock = IdentifierUtil.of("seedqueue", "textures/gui/wall/lock.png");
32 | do {
33 | try {
34 | lockTextures.add(new LockTexture(lock));
35 | } catch (IOException e) {
36 | SeedQueue.LOGGER.warn("Failed to read lock image texture: {}", lock, e);
37 | }
38 | } while (MinecraftClient.getInstance().getResourceManager().containsResource(lock = IdentifierUtil.of("seedqueue", "textures/gui/wall/lock-" + lockTextures.size() + ".png")));
39 | return lockTextures;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/debug/SeedQueueProfiler.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.debug;
2 |
3 | import me.contaria.seedqueue.SeedQueue;
4 | import me.contaria.seedqueue.SeedQueueConfig;
5 | import net.minecraft.client.MinecraftClient;
6 |
7 | /**
8 | * Provides methods to profile rendering on the Wall Screen without affecting the profiler during vanilla gameplay.
9 | * It will only profile when called from the Render Thread while being on the Wall Screen with {@link SeedQueueConfig#showDebugMenu} enabled.
10 | */
11 | public class SeedQueueProfiler {
12 | public static void push(String location) {
13 | if (shouldProfile()) {
14 | MinecraftClient.getInstance().getProfiler().push(location);
15 | }
16 | }
17 |
18 | public static void swap(String location) {
19 | if (shouldProfile()) {
20 | MinecraftClient.getInstance().getProfiler().swap(location);
21 | }
22 | }
23 |
24 | public static void pop() {
25 | if (shouldProfile()) {
26 | MinecraftClient.getInstance().getProfiler().pop();
27 | }
28 | }
29 |
30 | private static boolean shouldProfile() {
31 | return MinecraftClient.getInstance().isOnThread() && SeedQueue.isOnWall() && SeedQueue.config.showDebugMenu;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/debug/SeedQueueSystemInfo.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.debug;
2 |
3 | import com.google.gson.GsonBuilder;
4 | import com.sun.management.OperatingSystemMXBean;
5 | import me.contaria.seedqueue.SeedQueue;
6 | import org.lwjgl.opengl.GL11;
7 |
8 | import java.lang.management.ManagementFactory;
9 |
10 | public class SeedQueueSystemInfo {
11 | public static void logSystemInformation() {
12 | if (Boolean.parseBoolean(System.getProperty("seedqueue.logSystemInfo", "true"))) {
13 | SeedQueue.LOGGER.info("System Information (Logged by SeedQueue):");
14 | SeedQueue.LOGGER.info("Operating System: {}", System.getProperty("os.name"));
15 | SeedQueue.LOGGER.info("OS Version: {}", System.getProperty("os.version"));
16 | SeedQueue.LOGGER.info("CPU: {}", getCpuInfo());
17 | SeedQueue.LOGGER.info("GPU: {}", getGpuInfo());
18 | SeedQueue.LOGGER.info("Java Version: {}", System.getProperty("java.version"));
19 | SeedQueue.LOGGER.info("JVM Arguments: {}", getJavaArguments());
20 | SeedQueue.LOGGER.info("Total Physical Memory (MB): {}", getTotalPhysicalMemory());
21 | SeedQueue.LOGGER.info("Max Memory (MB): {}", getMaxAllocatedMemory());
22 | SeedQueue.LOGGER.info("Total Processors: {}", getTotalPhysicalProcessors());
23 | SeedQueue.LOGGER.info("Available Processors: {}", getAvailableProcessors());
24 | }
25 | }
26 |
27 | private static String getCpuInfo() {
28 | // see GLX#_init
29 | oshi.hardware.Processor[] processors = new oshi.SystemInfo().getHardware().getProcessors();
30 | return String.format("%dx %s", processors.length, processors[0]).replaceAll("\\s+", " ");
31 | }
32 |
33 | private static String getGpuInfo() {
34 | // see GlDebugInfo#getRenderer
35 | return GL11.glGetString(GL11.GL_RENDERER);
36 | }
37 |
38 | private static String getJavaArguments() {
39 | // Logs the java arguments being used by the JVM
40 | return String.join(" ", ManagementFactory.getRuntimeMXBean().getInputArguments());
41 | }
42 |
43 | private static long getTotalPhysicalMemory() {
44 | // Logs the total RAM on the system
45 | return ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class).getTotalPhysicalMemorySize() / (1024 * 1024);
46 | }
47 |
48 | private static long getMaxAllocatedMemory() {
49 | // Logs the max RAM the JVM will try to use
50 | return Runtime.getRuntime().maxMemory() / (1024 * 1024);
51 | }
52 |
53 | private static int getTotalPhysicalProcessors() {
54 | // Logs the total number of processors on the system
55 | // also includes the ones which are affected by affinity
56 | return ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
57 | }
58 |
59 | private static int getAvailableProcessors() {
60 | // Logs the available number of processors
61 | // excludes the ones which are affected by affinity
62 | return Runtime.getRuntime().availableProcessors();
63 | }
64 |
65 | public static void logConfigSettings() {
66 | if (Boolean.parseBoolean(System.getProperty("seedqueue.logConfigSettings", "true"))) {
67 | SeedQueue.LOGGER.info("SeedQueue Config settings: {}", new GsonBuilder().setPrettyPrinting().create().toJson(SeedQueue.config.container.toJson()));
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/debug/SeedQueueWatchdog.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.debug;
2 |
3 | import me.contaria.seedqueue.SeedQueue;
4 | import me.contaria.seedqueue.mixin.accessor.MinecraftClientAccessor;
5 | import net.minecraft.client.MinecraftClient;
6 |
7 | import java.util.Arrays;
8 |
9 | public class SeedQueueWatchdog {
10 | private static final Object LOCK = new Object();
11 | private static volatile Thread watchdog = null;
12 |
13 | public static void start() {
14 | if (!SeedQueue.isActive()) {
15 | throw new IllegalStateException("Tried to stop SeedQueue Watchdog while SeedQueue isn't running!");
16 | }
17 | if (SeedQueue.config.useWatchdog && watchdog == null) {
18 | watchdog = createWatchdog();
19 | }
20 | }
21 |
22 | public static void stop() {
23 | if (SeedQueue.isActive()) {
24 | throw new IllegalStateException("Tried to stop SeedQueue Watchdog while SeedQueue is still running!");
25 | }
26 | if (watchdog == null) {
27 | return;
28 | }
29 | synchronized (LOCK) {
30 | LOCK.notify();
31 | }
32 | try {
33 | watchdog.join();
34 | } catch (InterruptedException e) {
35 | SeedQueue.LOGGER.warn("SeedQueue Watchdog was interrupted while stopping!");
36 | }
37 | watchdog = null;
38 | }
39 |
40 | private static Thread createWatchdog() {
41 | Thread thread = new Thread(() -> {
42 | try {
43 | while (SeedQueue.isActive()) {
44 | synchronized (LOCK) {
45 | Thread sqThread = SeedQueue.getThread();
46 | if (sqThread != null) {
47 | SeedQueue.LOGGER.info("WATCHDOG | Main: {}", Arrays.toString(((MinecraftClientAccessor) MinecraftClient.getInstance()).seedQueue$getThread().getStackTrace()));
48 | SeedQueue.LOGGER.info("WATCHDOG | SeedQueue: {}", Arrays.toString(sqThread.getStackTrace()));
49 | }
50 | LOCK.wait(10000);
51 | }
52 | }
53 | } catch (InterruptedException e) {
54 | SeedQueue.LOGGER.warn("SeedQueue Watchdog was interrupted while running!");
55 | }
56 | });
57 | thread.setDaemon(true);
58 | thread.setPriority(3);
59 | thread.setName("SeedQueue Watchdog");
60 | thread.start();
61 | return thread;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/gui/SeedQueueCrashToast.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.gui;
2 |
3 | import net.minecraft.client.MinecraftClient;
4 | import net.minecraft.client.toast.Toast;
5 | import net.minecraft.client.toast.ToastManager;
6 | import net.minecraft.client.util.math.MatrixStack;
7 | import net.minecraft.text.StringRenderable;
8 | import net.minecraft.text.Text;
9 |
10 | import java.util.List;
11 |
12 | public class SeedQueueCrashToast implements Toast {
13 | private final Text title;
14 | private final List description;
15 | private boolean hasStarted;
16 | private long startTime = Long.MAX_VALUE;
17 |
18 | public SeedQueueCrashToast(Text title, Text description) {
19 | this.title = title;
20 | this.description = MinecraftClient.getInstance().textRenderer.wrapLines(description, this.getWidth() - 7);
21 | }
22 |
23 | @Override
24 | public Visibility draw(MatrixStack matrices, ToastManager manager, long startTime) {
25 | if (!this.hasStarted && manager.getGame().isWindowFocused()) {
26 | this.startTime = startTime;
27 | this.hasStarted = true;
28 | }
29 |
30 | manager.getGame().getTextureManager().bindTexture(TOASTS_TEX);
31 | if (this.description.size() < 2) {
32 | manager.drawTexture(matrices, 0, 0, 0, 0, this.getWidth(), this.getHeight());
33 | } else {
34 | manager.drawTexture(matrices, 0, 0, 0, 0, this.getWidth(), 11);
35 | int y = 8;
36 | for (int i = 0; i < this.description.size(); i++) {
37 | manager.drawTexture(matrices, 0, y, 0, 11, this.getWidth(), 10);
38 | y += 10;
39 | }
40 | manager.drawTexture(matrices, 0, y, 0, 21, this.getWidth(), 11);
41 | }
42 |
43 | manager.getGame().textRenderer.draw(matrices, this.title, 7.0f, 7.0f, 0xFFFF00 | 0xFF000000);
44 |
45 | float y = 18.0f;
46 | for (StringRenderable description : this.description) {
47 | manager.getGame().textRenderer.draw(matrices, description, 7.0f, y, -1);
48 | y += 10.0f;
49 | }
50 |
51 | return startTime - this.startTime < 5000L ? Toast.Visibility.SHOW : Toast.Visibility.HIDE;
52 | }
53 |
54 | @Override
55 | public int getHeight() {
56 | return Toast.super.getHeight() + Math.max(1, this.description.size() - 1) * 10;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/gui/config/SeedQueueKeyButtonWidget.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.gui.config;
2 |
3 | import me.contaria.speedrunapi.util.TextUtil;
4 | import net.minecraft.client.gui.widget.AbstractPressableButtonWidget;
5 | import net.minecraft.client.util.InputUtil;
6 | import net.minecraft.text.MutableText;
7 | import net.minecraft.text.Text;
8 |
9 | public class SeedQueueKeyButtonWidget extends AbstractPressableButtonWidget {
10 | private static final Text UNKNOWN_KEY = InputUtil.UNKNOWN_KEY.getLocalizedText();
11 |
12 | private final SeedQueueKeybindingsListWidget.KeyEntry entry;
13 |
14 | public SeedQueueKeyButtonWidget(SeedQueueKeybindingsListWidget.KeyEntry entry) {
15 | this(entry, UNKNOWN_KEY);
16 | }
17 |
18 | public SeedQueueKeyButtonWidget(SeedQueueKeybindingsListWidget.KeyEntry entry, Text message) {
19 | super(0, 0, 75, 20, message);
20 | this.entry = entry;
21 | }
22 |
23 | @Override
24 | public void onPress() {
25 | this.entry.selectButton(this);
26 | }
27 |
28 | @Override
29 | protected MutableText getNarrationMessage() {
30 | if (UNKNOWN_KEY.equals(this.getMessage())) {
31 | return TextUtil.translatable("narrator.controls.unbound", this.entry.title);
32 | }
33 | return TextUtil.translatable("narrator.controls.bound", this.entry.title, this.getMessage());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/gui/config/SeedQueueKeybindingsScreen.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.gui.config;
2 |
3 | import me.contaria.seedqueue.keybindings.SeedQueueMultiKeyBinding;
4 | import me.contaria.speedrunapi.util.TextUtil;
5 | import net.minecraft.client.gui.screen.Screen;
6 | import net.minecraft.client.gui.screen.ScreenTexts;
7 | import net.minecraft.client.gui.widget.ButtonWidget;
8 | import net.minecraft.client.util.InputUtil;
9 | import net.minecraft.client.util.math.MatrixStack;
10 | import org.lwjgl.glfw.GLFW;
11 |
12 | public class SeedQueueKeybindingsScreen extends Screen {
13 | private final Screen parent;
14 | protected final SeedQueueMultiKeyBinding[] keyBindings;
15 | protected SeedQueueKeybindingsListWidget.KeyEntry focusedBinding;
16 | private SeedQueueKeybindingsListWidget keyBindingListWidget;
17 |
18 | public SeedQueueKeybindingsScreen(Screen parent, SeedQueueMultiKeyBinding... keyBindings) {
19 | super(TextUtil.translatable("seedqueue.menu.keys"));
20 | this.parent = parent;
21 | this.keyBindings = keyBindings;
22 | }
23 |
24 | @Override
25 | protected void init() {
26 | this.keyBindingListWidget = new SeedQueueKeybindingsListWidget(this, this.client);
27 | this.children.add(this.keyBindingListWidget);
28 | this.addButton(new ButtonWidget(this.width / 2 - 100, this.height - 27, 200, 20, ScreenTexts.DONE, button -> this.onClose()));
29 | }
30 |
31 | @Override
32 | public boolean mouseClicked(double mouseX, double mouseY, int button) {
33 | if (this.focusedBinding != null) {
34 | this.focusedBinding.pressKey(InputUtil.Type.MOUSE.createFromCode(button));
35 | this.focusedBinding = null;
36 | return true;
37 | }
38 | return super.mouseClicked(mouseX, mouseY, button);
39 | }
40 |
41 | @Override
42 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
43 | if (this.focusedBinding != null) {
44 | this.focusedBinding.pressKey(keyCode == GLFW.GLFW_KEY_ESCAPE ? InputUtil.UNKNOWN_KEY : InputUtil.fromKeyCode(keyCode, scanCode));
45 | this.focusedBinding = null;
46 | return true;
47 | }
48 | return super.keyPressed(keyCode, scanCode, modifiers);
49 | }
50 |
51 | @Override
52 | public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
53 | this.renderBackground(matrices);
54 | this.keyBindingListWidget.render(matrices, mouseX, mouseY, delta);
55 | this.drawCenteredText(matrices, this.textRenderer, this.title, this.width / 2, 10, 0xFFFFFF);
56 | super.render(matrices, mouseX, mouseY, delta);
57 | }
58 |
59 | @Override
60 | public void onClose() {
61 | assert this.client != null;
62 | this.client.openScreen(this.parent);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/gui/config/SeedQueueWindowSizeWidget.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.gui.config;
2 |
3 | import me.contaria.seedqueue.SeedQueueConfig;
4 | import me.contaria.speedrunapi.util.TextUtil;
5 | import net.minecraft.client.MinecraftClient;
6 | import net.minecraft.client.gui.Element;
7 | import net.minecraft.client.gui.ParentElement;
8 | import net.minecraft.client.gui.widget.AbstractButtonWidget;
9 | import net.minecraft.client.gui.widget.TextFieldWidget;
10 | import net.minecraft.client.util.math.MatrixStack;
11 | import net.minecraft.text.StringRenderable;
12 | import org.jetbrains.annotations.Nullable;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 | import java.util.Optional;
17 |
18 | public class SeedQueueWindowSizeWidget extends AbstractButtonWidget implements ParentElement {
19 | private static final StringRenderable X = StringRenderable.plain("X");
20 |
21 | private final SeedQueueConfig.WindowSize windowSize;
22 | private final TextFieldWidget widthWidget;
23 | private final TextFieldWidget heightWidget;
24 |
25 | @Nullable
26 | private Element focused;
27 | private boolean isDragging;
28 |
29 | public SeedQueueWindowSizeWidget(SeedQueueConfig.WindowSize windowSize) {
30 | super(0, 0, 150, 20, TextUtil.empty());
31 | this.windowSize = windowSize;
32 | this.widthWidget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 65, 20, TextUtil.empty());
33 | this.widthWidget.setText(String.valueOf(this.windowSize.width()));
34 | this.widthWidget.setChangedListener(text -> {
35 | if (text.isEmpty()) {
36 | // set width to 0 without updating the text
37 | this.windowSize.setWidth(0);
38 | return;
39 | }
40 | this.windowSize.setWidth(Integer.parseUnsignedInt(text));
41 | String newText = String.valueOf(this.windowSize.width());
42 | if (!text.equals(newText)) {
43 | this.widthWidget.setText(newText);
44 | }
45 | });
46 | this.widthWidget.setTextPredicate(text -> {
47 | try {
48 | return text.isEmpty() || Integer.parseUnsignedInt(text) >= 0;
49 | } catch (NumberFormatException e) {
50 | return false;
51 | }
52 | });
53 | this.heightWidget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 65, 20, TextUtil.empty());
54 | this.heightWidget.setText(String.valueOf(this.windowSize.height()));
55 | this.heightWidget.setChangedListener(text -> {
56 | if (text.isEmpty()) {
57 | this.windowSize.setHeight(0);
58 | return;
59 | }
60 | this.windowSize.setHeight(Integer.parseUnsignedInt(text));
61 | String newText = String.valueOf(this.windowSize.height());
62 | if (!text.equals(newText)) {
63 | this.heightWidget.setText(newText);
64 | }
65 | });
66 | this.heightWidget.setTextPredicate(text -> {
67 | try {
68 | return text.isEmpty() || Integer.parseUnsignedInt(text) >= 0;
69 | } catch (NumberFormatException e) {
70 | return false;
71 | }
72 | });
73 | }
74 |
75 | @Override
76 | public Optional hoveredElement(double mouseX, double mouseY) {
77 | return ParentElement.super.hoveredElement(mouseX, mouseY);
78 | }
79 |
80 | @Override
81 | public boolean mouseClicked(double mouseX, double mouseY, int button) {
82 | return ParentElement.super.mouseClicked(mouseX, mouseY, button);
83 | }
84 |
85 | @Override
86 | public boolean mouseReleased(double mouseX, double mouseY, int button) {
87 | return ParentElement.super.mouseReleased(mouseX, mouseY, button);
88 | }
89 |
90 | @Override
91 | public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
92 | return ParentElement.super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
93 | }
94 |
95 | @Override
96 | public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
97 | return ParentElement.super.mouseScrolled(mouseX, mouseY, amount);
98 | }
99 |
100 | @Override
101 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
102 | return ParentElement.super.keyPressed(keyCode, scanCode, modifiers);
103 | }
104 |
105 | @Override
106 | public boolean keyReleased(int keyCode, int scanCode, int modifiers) {
107 | return ParentElement.super.keyReleased(keyCode, scanCode, modifiers);
108 | }
109 |
110 | @Override
111 | public boolean charTyped(char chr, int keyCode) {
112 | return ParentElement.super.charTyped(chr, keyCode);
113 | }
114 |
115 | @Override
116 | public void setInitialFocus(@Nullable Element element) {
117 | ParentElement.super.setInitialFocus(element);
118 | }
119 |
120 | @Override
121 | public void focusOn(@Nullable Element element) {
122 | ParentElement.super.focusOn(element);
123 | }
124 |
125 | @Override
126 | public boolean changeFocus(boolean lookForwards) {
127 | return ParentElement.super.changeFocus(lookForwards);
128 | }
129 |
130 | @Override
131 | public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) {
132 | this.widthWidget.x = this.x;
133 | this.widthWidget.y = this.y;
134 | this.widthWidget.render(matrices, mouseX, mouseY, delta);
135 | this.drawCenteredText(matrices, MinecraftClient.getInstance().textRenderer, X, this.x + this.width / 2, this.y + (this.height - MinecraftClient.getInstance().textRenderer.fontHeight) / 2, 0xFFFFFF);
136 | this.heightWidget.x = this.x + 85;
137 | this.heightWidget.y = this.y;
138 | this.heightWidget.render(matrices, mouseX, mouseY, delta);
139 | }
140 |
141 | @Override
142 | public List extends Element> children() {
143 | List children = new ArrayList<>();
144 | children.add(this.widthWidget);
145 | children.add(this.heightWidget);
146 | return children;
147 | }
148 |
149 | @Override
150 | public final boolean isDragging() {
151 | return this.isDragging;
152 | }
153 |
154 | @Override
155 | public final void setDragging(boolean dragging) {
156 | this.isDragging = dragging;
157 | }
158 |
159 | @Override
160 | @Nullable
161 | public Element getFocused() {
162 | return this.focused;
163 | }
164 |
165 | @Override
166 | public void setFocused(@Nullable Element focused) {
167 | this.focused = focused;
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/gui/wall/SeedQueueBenchmarkToast.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.gui.wall;
2 |
3 | import me.contaria.speedrunapi.util.TextUtil;
4 | import net.minecraft.client.toast.Toast;
5 | import net.minecraft.client.toast.ToastManager;
6 | import net.minecraft.client.util.math.MatrixStack;
7 | import net.minecraft.text.Text;
8 | import net.minecraft.util.Util;
9 |
10 | public class SeedQueueBenchmarkToast implements Toast {
11 | private final SeedQueueWallScreen wall;
12 | private final Text title;
13 |
14 | private boolean finished;
15 | private boolean fadeOut;
16 |
17 | public SeedQueueBenchmarkToast(SeedQueueWallScreen wall) {
18 | this.wall = wall;
19 | this.title = TextUtil.translatable("seedqueue.menu.benchmark.title");
20 | }
21 |
22 | @Override
23 | public Visibility draw(MatrixStack matrices, ToastManager manager, long startTime) {
24 | manager.getGame().getTextureManager().bindTexture(TOASTS_TEX);
25 | manager.drawTexture(matrices, 0, 0, 0, 0, this.getWidth(), this.getHeight());
26 | manager.getGame().textRenderer.draw(matrices, this.title, 7.0f, 7.0f, 0xFFFF00 | 0xFF000000);
27 |
28 | this.finished |= !this.wall.isBenchmarking();
29 |
30 | if (this.finished && !this.fadeOut && !this.wall.showFinishedBenchmarkResults) {
31 | this.fadeOut = true;
32 | }
33 |
34 | double time = (this.finished ? this.wall.benchmarkFinish : Util.getMeasuringTimeMs()) - this.wall.benchmarkStart;
35 | double rps = Math.round(this.wall.benchmarkedSeeds / (time / 10000.0)) / 10.0;
36 | manager.getGame().textRenderer.draw(matrices, TextUtil.translatable("seedqueue.menu.benchmark.result", this.wall.benchmarkedSeeds, Math.round(time / 1000.0), rps), 7.0f, 18.0f, -1);
37 |
38 | return this.fadeOut ? Visibility.HIDE : Visibility.SHOW;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/interfaces/SQMinecraftServer.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.interfaces;
2 |
3 | import me.contaria.seedqueue.SeedQueueEntry;
4 |
5 | import java.util.Optional;
6 | import java.util.concurrent.Executor;
7 |
8 | public interface SQMinecraftServer {
9 |
10 | Optional seedQueue$getEntry();
11 |
12 | boolean seedQueue$inQueue();
13 |
14 | void seedQueue$setEntry(SeedQueueEntry entry);
15 |
16 | void seedQueue$tryPausingServer();
17 |
18 | boolean seedQueue$shouldPause();
19 |
20 | boolean seedQueue$isPaused();
21 |
22 | boolean seedQueue$isScheduledToPause();
23 |
24 | void seedQueue$schedulePause();
25 |
26 | void seedQueue$unpause();
27 |
28 | boolean seedQueue$isDiscarded();
29 |
30 | void seedQueue$setExecutor(Executor executor);
31 |
32 | void seedQueue$resetExecutor();
33 |
34 | int seedQueue$incrementAndGetEntityID();
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/interfaces/SQSoundManager.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.interfaces;
2 |
3 | public interface SQSoundManager {
4 | void seedQueue$stopAllExceptSeedQueueSounds();
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/interfaces/SQSoundSystem.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.interfaces;
2 |
3 | public interface SQSoundSystem {
4 | void seedQueue$stopAllExceptSeedQueueSounds();
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/interfaces/SQWorldGenerationProgressLogger.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.interfaces;
2 |
3 | public interface SQWorldGenerationProgressLogger {
4 |
5 | void seedQueue$mute();
6 |
7 | void seedQueue$unmute();
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/interfaces/SQWorldGenerationProgressTracker.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.interfaces;
2 |
3 | import net.minecraft.client.gui.WorldGenerationProgressTracker;
4 |
5 | import java.util.Optional;
6 |
7 | public interface SQWorldGenerationProgressTracker {
8 | /**
9 | * @return a copy of the WorldGenerationProgressTracker which was made after the milliseconds provided by seedQueue$makeCopyAfter
10 | */
11 | boolean seedQueue$shouldFreeze();
12 | /**
13 | * @return a copy of the WorldGenerationProgressTracker which was made after the milliseconds provided by seedQueue$makeCopyAfter
14 | */
15 | Optional seedQueue$getFrozenCopy();
16 |
17 | /**
18 | * Calculates a more accurate progress percentage by counting chunks in rings and only including outer layers if inner layers are complete. This tries to address the effect ocean spawns have on inflating the progress percentage in vanilla's {@link net.minecraft.server.WorldGenerationProgressLogger}
19 | *
20 | * @return The percent of spawn chunks that are generated, in the range of 0 to 100.
21 | */
22 | int seedQueue$getProgressPercentage();
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/interfaces/sodium/SQChunkBuilder$WorkerRunnable.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.interfaces.sodium;
2 |
3 | import net.minecraft.world.World;
4 |
5 | public interface SQChunkBuilder$WorkerRunnable {
6 |
7 | void seedQueue$setWorldForRenderCache(World world);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/interfaces/worldpreview/SQWorldRenderer.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.interfaces.worldpreview;
2 |
3 | import net.minecraft.client.render.Camera;
4 | import net.minecraft.client.util.math.MatrixStack;
5 | import net.minecraft.util.math.Matrix4f;
6 |
7 | public interface SQWorldRenderer {
8 |
9 | void seedQueue$buildChunks(MatrixStack matrices, Camera camera, Matrix4f projectionMatrix);
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/keybindings/SeedQueueKeyBindings.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.keybindings;
2 |
3 | import org.lwjgl.glfw.GLFW;
4 |
5 | public class SeedQueueKeyBindings {
6 | public static final SeedQueueMultiKeyBinding play = new SeedQueueMultiKeyBinding("seedqueue.key.play", GLFW.GLFW_KEY_R);
7 | public static final SeedQueueMultiKeyBinding lock = new SeedQueueMultiKeyBinding("seedqueue.key.lock", GLFW.GLFW_KEY_L);
8 | public static final SeedQueueMultiKeyBinding reset = new SeedQueueMultiKeyBinding("seedqueue.key.reset", GLFW.GLFW_KEY_E);
9 | public static final SeedQueueMultiKeyBinding resetAll = new SeedQueueMultiKeyBinding("seedqueue.key.resetAll", GLFW.GLFW_KEY_T);
10 | public static final SeedQueueMultiKeyBinding focusReset = new SeedQueueMultiKeyBinding("seedqueue.key.focusReset", GLFW.GLFW_KEY_F);
11 | public static final SeedQueueMultiKeyBinding resetColumn = new SeedQueueMultiKeyBinding("seedqueue.key.resetColumn");
12 | public static final SeedQueueMultiKeyBinding resetRow = new SeedQueueMultiKeyBinding("seedqueue.key.resetRow");
13 | public static final SeedQueueMultiKeyBinding playNextLock = new SeedQueueMultiKeyBinding("seedqueue.key.playNextLock");
14 | public static final SeedQueueMultiKeyBinding scheduleJoin = new SeedQueueMultiKeyBinding("seedqueue.key.scheduleJoin");
15 | public static final SeedQueueMultiKeyBinding scheduleAll = new SeedQueueMultiKeyBinding("seedqueue.key.scheduleAll");
16 | public static final SeedQueueMultiKeyBinding startBenchmark = new SeedQueueMultiKeyBinding("seedqueue.key.startBenchmark");
17 | public static final SeedQueueMultiKeyBinding cancelBenchmark = new SeedQueueMultiKeyBinding("seedqueue.key.cancelBenchmark");
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/keybindings/SeedQueueMultiKeyBinding.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.keybindings;
2 |
3 | import com.google.gson.JsonArray;
4 | import com.google.gson.JsonElement;
5 | import com.google.gson.JsonObject;
6 | import com.google.gson.JsonPrimitive;
7 | import net.minecraft.client.MinecraftClient;
8 | import net.minecraft.client.util.InputUtil;
9 | import org.jetbrains.annotations.Nullable;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | public class SeedQueueMultiKeyBinding {
15 | private final String translationKey;
16 | private final String category;
17 |
18 | private InputUtil.Key primaryKey;
19 | private final List secondaryKeys;
20 | private final List blockingKeys;
21 |
22 | public SeedQueueMultiKeyBinding(String translationKey) {
23 | this(translationKey, "seedqueue.key.categories.builtin");
24 | }
25 |
26 | public SeedQueueMultiKeyBinding(String translationKey, String category) {
27 | this(translationKey, category, InputUtil.UNKNOWN_KEY);
28 | }
29 |
30 | public SeedQueueMultiKeyBinding(String translationKey, int code) {
31 | this(translationKey, "seedqueue.key.categories.builtin", code);
32 | }
33 |
34 | public SeedQueueMultiKeyBinding(String translationKey, String category, int code) {
35 | this(translationKey, category, InputUtil.Type.KEYSYM, code);
36 | }
37 |
38 | public SeedQueueMultiKeyBinding(String translationKey, String category, InputUtil.Type type, int code) {
39 | this(translationKey, category, type.createFromCode(code));
40 | }
41 |
42 | protected SeedQueueMultiKeyBinding(String translationKey, String category, InputUtil.Key primaryKey) {
43 | this.translationKey = translationKey;
44 | this.category = category;
45 | this.primaryKey = primaryKey;
46 | this.secondaryKeys = new ArrayList<>();
47 | this.blockingKeys = new ArrayList<>();
48 | }
49 |
50 | public boolean matchesKey(int keyCode, int scanCode) {
51 | return keyCode == InputUtil.UNKNOWN_KEY.getCode() ? this.matchesPrimary(InputUtil.Type.SCANCODE, scanCode) : this.matchesPrimary(InputUtil.Type.KEYSYM, keyCode) && this.areSecondaryKeysDown() && this.areBlockingKeysNotDown();
52 | }
53 |
54 | public boolean matchesMouse(int code) {
55 | return this.matchesPrimary(InputUtil.Type.MOUSE, code) && this.areSecondaryKeysDown() && this.areBlockingKeysNotDown();
56 | }
57 |
58 | private boolean matchesPrimary(InputUtil.Type type, int code) {
59 | return this.primaryKey.getCategory() == type && this.primaryKey.getCode() == code;
60 | }
61 |
62 | private boolean areSecondaryKeysDown() {
63 | for (InputUtil.Key key : this.secondaryKeys) {
64 | if (!InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow().getHandle(), key.getCode())) {
65 | return false;
66 | }
67 | }
68 | return true;
69 | }
70 |
71 | private boolean areBlockingKeysNotDown() {
72 | for (InputUtil.Key key : this.blockingKeys) {
73 | if (InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow().getHandle(), key.getCode())) {
74 | return false;
75 | }
76 | }
77 | return true;
78 | }
79 |
80 | public String getTranslationKey() {
81 | return this.translationKey;
82 | }
83 |
84 | public String getCategory() {
85 | return this.category;
86 | }
87 |
88 | public InputUtil.Key getPrimaryKey() {
89 | return this.primaryKey;
90 | }
91 |
92 | public void setPrimaryKey(InputUtil.Key key) {
93 | this.primaryKey = key;
94 | }
95 |
96 | public void setSecondaryKey(int index, InputUtil.Key key) {
97 | this.secondaryKeys.set(index, key);
98 | }
99 |
100 | public void addSecondaryKey(InputUtil.Key key) {
101 | this.secondaryKeys.add(key);
102 | }
103 |
104 | public void removeSecondaryKey(int index) {
105 |
106 | this.secondaryKeys.remove(index);
107 | }
108 |
109 | public List getSecondaryKeys() {
110 | return this.secondaryKeys;
111 | }
112 |
113 | public void setBlockingKey(int index, InputUtil.Key key) {
114 | this.blockingKeys.set(index, key);
115 | }
116 |
117 | public void addBlockingKey(InputUtil.Key key) {
118 | this.blockingKeys.add(key);
119 | }
120 |
121 | public void removeBlockingKey(int index) {
122 | this.blockingKeys.remove(index);
123 | }
124 |
125 | public List getBlockingKeys() {
126 | return this.blockingKeys;
127 | }
128 |
129 | public JsonElement toJson() {
130 | if (this.secondaryKeys.isEmpty() && this.blockingKeys.isEmpty()) {
131 | return new JsonPrimitive(this.primaryKey.getTranslationKey());
132 | }
133 |
134 | JsonObject jsonObject = new JsonObject();
135 | jsonObject.add("primary", new JsonPrimitive(this.primaryKey.getTranslationKey()));
136 |
137 | JsonArray secondary = new JsonArray();
138 | for (InputUtil.Key key : this.secondaryKeys) {
139 | secondary.add(new JsonPrimitive(key.getTranslationKey()));
140 | }
141 | jsonObject.add("secondary", secondary);
142 |
143 | JsonArray blocking = new JsonArray();
144 | for (InputUtil.Key key : this.blockingKeys) {
145 | blocking.add(new JsonPrimitive(key.getTranslationKey()));
146 | }
147 | jsonObject.add("blocking", blocking);
148 |
149 | return jsonObject;
150 | }
151 |
152 | public void fromJson(@Nullable JsonElement jsonElement) {
153 | if (jsonElement == null) {
154 | return;
155 | }
156 |
157 | this.secondaryKeys.clear();
158 | this.blockingKeys.clear();
159 |
160 | if (!jsonElement.isJsonObject()) {
161 | this.setPrimaryKey(InputUtil.fromTranslationKey(jsonElement.getAsString()));
162 | return;
163 | }
164 |
165 | JsonObject jsonObject = jsonElement.getAsJsonObject();
166 |
167 | this.setPrimaryKey(InputUtil.fromTranslationKey(jsonObject.get("primary").getAsString()));
168 | for (JsonElement key : jsonObject.getAsJsonArray("secondary")) {
169 | this.addSecondaryKey(InputUtil.fromTranslationKey(key.getAsString()));
170 | }
171 | for (JsonElement key : jsonObject.getAsJsonArray("blocking")) {
172 | this.addBlockingKey(InputUtil.fromTranslationKey(key.getAsString()));
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/accessor/CameraAccessor.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.accessor;
2 |
3 | import net.minecraft.client.render.Camera;
4 | import org.spongepowered.asm.mixin.Mixin;
5 | import org.spongepowered.asm.mixin.gen.Accessor;
6 |
7 | @Mixin(Camera.class)
8 | public interface CameraAccessor {
9 | @Accessor("inverseView")
10 | boolean seedQueue$isInverseView();
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/accessor/DebugHudAccessor.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.accessor;
2 |
3 | import net.minecraft.client.gui.hud.DebugHud;
4 | import net.minecraft.client.util.math.MatrixStack;
5 | import net.minecraft.util.MetricsData;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.gen.Invoker;
8 |
9 | @Mixin(DebugHud.class)
10 | public interface DebugHudAccessor {
11 | @Invoker("drawMetricsData")
12 | void seedQueue$drawMetricsData(MatrixStack matrixStack, MetricsData metricsData, int i, int j, boolean bl);
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/accessor/EntityAccessor.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.accessor;
2 |
3 | import net.minecraft.entity.Entity;
4 | import org.spongepowered.asm.mixin.Mixin;
5 | import org.spongepowered.asm.mixin.gen.Accessor;
6 |
7 | import java.util.concurrent.atomic.AtomicInteger;
8 |
9 | @Mixin(Entity.class)
10 | public interface EntityAccessor {
11 | @Accessor("MAX_ENTITY_ID")
12 | static AtomicInteger seedQueue$getMAX_ENTITY_ID() {
13 | throw new UnsupportedOperationException();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/accessor/MinecraftClientAccessor.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.accessor;
2 |
3 | import net.minecraft.client.MinecraftClient;
4 | import org.spongepowered.asm.mixin.Mixin;
5 | import org.spongepowered.asm.mixin.gen.Accessor;
6 | import org.spongepowered.asm.mixin.gen.Invoker;
7 |
8 | @Mixin(MinecraftClient.class)
9 | public interface MinecraftClientAccessor {
10 | @Invoker("render")
11 | void seedQueue$render(boolean tick);
12 |
13 | @Accessor("thread")
14 | Thread seedQueue$getThread();
15 |
16 | @Invoker("handleProfilerKeyPress")
17 | void seedQueue$handleProfilerKeyPress(int digit);
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/accessor/MinecraftServerAccessor.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.accessor;
2 |
3 | import com.mojang.authlib.GameProfileRepository;
4 | import com.mojang.authlib.minecraft.MinecraftSessionService;
5 | import net.minecraft.server.MinecraftServer;
6 | import net.minecraft.util.UserCache;
7 | import net.minecraft.util.registry.RegistryTracker;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.Mutable;
10 | import org.spongepowered.asm.mixin.gen.Accessor;
11 |
12 | @Mixin(MinecraftServer.class)
13 | public interface MinecraftServerAccessor {
14 | @Accessor("running")
15 | void seedQueue$setRunning(boolean running);
16 |
17 | @Mutable
18 | @Accessor("userCache")
19 | void seedQueue$setUserCache(UserCache userCache);
20 |
21 | @Mutable
22 | @Accessor("gameProfileRepo")
23 | void seedQueue$setGameProfileRepo(GameProfileRepository gameProfileRepo);
24 |
25 | @Mutable
26 | @Accessor("sessionService")
27 | void seedQueue$setSessionService(MinecraftSessionService sessionService);
28 |
29 | @Accessor("dimensionTracker")
30 | RegistryTracker.Modifiable seedQueue$getDimensionTracker();
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/accessor/PlayerEntityAccessor.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.accessor;
2 |
3 | import net.minecraft.entity.data.TrackedData;
4 | import net.minecraft.entity.player.PlayerEntity;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.gen.Accessor;
7 |
8 | @Mixin(PlayerEntity.class)
9 | public interface PlayerEntityAccessor {
10 | @Accessor("PLAYER_MODEL_PARTS")
11 | static TrackedData seedQueue$getPLAYER_MODEL_PARTS() {
12 | throw new UnsupportedOperationException();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/accessor/UtilAccessor.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.accessor;
2 |
3 | import net.minecraft.util.Util;
4 | import org.spongepowered.asm.mixin.Mixin;
5 | import org.spongepowered.asm.mixin.gen.Invoker;
6 |
7 | import java.util.concurrent.ExecutorService;
8 |
9 | @Mixin(Util.class)
10 | public interface UtilAccessor {
11 | @Invoker("attemptShutdown")
12 | static void seedQueue$attemptShutdown(ExecutorService executorService) {
13 | throw new UnsupportedOperationException();
14 | }
15 |
16 | @Invoker("uncaughtExceptionHandler")
17 | static void seedQueue$uncaughtExceptionHandler(Thread thread, Throwable throwable) {
18 | throw new UnsupportedOperationException();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/accessor/WorldGenerationProgressTrackerAccessor.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.accessor;
2 |
3 | import net.minecraft.client.gui.WorldGenerationProgressTracker;
4 | import net.minecraft.server.WorldGenerationProgressLogger;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.gen.Accessor;
7 |
8 | @Mixin(WorldGenerationProgressTracker.class)
9 | public interface WorldGenerationProgressTrackerAccessor {
10 | @Accessor
11 | WorldGenerationProgressLogger getProgressLogger();
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/accessor/WorldRendererAccessor.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.accessor;
2 |
3 | import net.minecraft.client.render.WorldRenderer;
4 | import net.minecraft.client.world.ClientWorld;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.gen.Accessor;
7 | import org.spongepowered.asm.mixin.gen.Invoker;
8 |
9 | @Mixin(WorldRenderer.class)
10 | public interface WorldRendererAccessor {
11 | @Accessor("world")
12 | ClientWorld seedQueue$getWorld();
13 |
14 | @Invoker("getCompletedChunkCount")
15 | int seedQueue$getCompletedChunkCount();
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/client/CreateWorldScreenMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.client;
2 |
3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
4 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
5 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
6 | import me.contaria.seedqueue.SeedQueue;
7 | import me.contaria.seedqueue.SeedQueueException;
8 | import net.minecraft.client.Keyboard;
9 | import net.minecraft.client.MinecraftClient;
10 | import net.minecraft.client.gui.screen.Screen;
11 | import net.minecraft.client.gui.screen.world.CreateWorldScreen;
12 | import org.spongepowered.asm.mixin.Mixin;
13 | import org.spongepowered.asm.mixin.injection.At;
14 | import org.spongepowered.asm.mixin.injection.Inject;
15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
16 |
17 | @Mixin(CreateWorldScreen.class)
18 | public abstract class CreateWorldScreenMixin {
19 |
20 | @WrapWithCondition(
21 | method = "createLevel",
22 | at = @At(
23 | value = "INVOKE",
24 | target = "Lnet/minecraft/client/MinecraftClient;setScreenAndRender(Lnet/minecraft/client/gui/screen/Screen;)V"
25 | )
26 | )
27 | private boolean cancelRenderingScreen(MinecraftClient client, Screen screen) {
28 | return !SeedQueue.inQueue();
29 | }
30 |
31 | @WrapOperation(
32 | method = "createLevel",
33 | at = @At(
34 | value = "INVOKE",
35 | target = "Lnet/minecraft/client/MinecraftClient;setScreenAndRender(Lnet/minecraft/client/gui/screen/Screen;)V"
36 | )
37 | )
38 | private void skipIntermissionScreen(MinecraftClient client, Screen screen, Operation original) {
39 | if (SeedQueue.currentEntry != null) {
40 | client.openScreen(screen);
41 | } else {
42 | original.call(client, screen);
43 | }
44 | }
45 |
46 | @Inject(
47 | method = "createLevel",
48 | at = @At(
49 | value = "RETURN",
50 | ordinal = 0
51 | )
52 | )
53 | private void throwSeedQueueException(CallbackInfo ci) throws SeedQueueException {
54 | if (SeedQueue.inQueue()) {
55 | throw new SeedQueueException("Failed to copy datapacks to world!");
56 | }
57 | }
58 |
59 | @WrapWithCondition(
60 | method = "init",
61 | at = @At(
62 | value = "INVOKE",
63 | target = "Lnet/minecraft/client/Keyboard;enableRepeatEvents(Z)V"
64 | )
65 | )
66 | private boolean doNotEnableRepeatEventsInQueue(Keyboard keyboard, boolean repeatEvents) {
67 | return !SeedQueue.inQueue();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/client/WorldGenerationProgressLoggerMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.client;
2 |
3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
4 | import me.contaria.seedqueue.interfaces.SQWorldGenerationProgressLogger;
5 | import net.minecraft.server.WorldGenerationProgressLogger;
6 | import org.apache.logging.log4j.Logger;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.Unique;
9 | import org.spongepowered.asm.mixin.injection.At;
10 |
11 | @Mixin(WorldGenerationProgressLogger.class)
12 | public abstract class WorldGenerationProgressLoggerMixin implements SQWorldGenerationProgressLogger {
13 |
14 | @Unique
15 | private boolean muted;
16 |
17 | @WrapWithCondition(
18 | method = "setChunkStatus",
19 | at = @At(
20 | value = "INVOKE",
21 | target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;)V",
22 | remap = false
23 | )
24 | )
25 | private boolean muteWorldGenProgress_inQueue(Logger logger, String s) {
26 | return !this.muted;
27 | }
28 |
29 | @WrapWithCondition(
30 | method = "stop",
31 | at = @At(
32 | value = "INVOKE",
33 | target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;)V",
34 | remap = false
35 | )
36 | )
37 | private boolean muteWorldGenProgress_inQueue(Logger logger, String s, Object o) {
38 | return !this.muted;
39 | }
40 |
41 | @Override
42 | public void seedQueue$mute() {
43 | this.muted = true;
44 | }
45 |
46 | @Override
47 | public void seedQueue$unmute() {
48 | this.muted = false;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/client/WorldGenerationProgressTrackerMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.client;
2 |
3 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
4 | import me.contaria.seedqueue.SeedQueue;
5 | import me.contaria.seedqueue.interfaces.SQWorldGenerationProgressTracker;
6 | import net.minecraft.client.gui.WorldGenerationProgressTracker;
7 | import net.minecraft.util.Util;
8 | import net.minecraft.util.math.ChunkPos;
9 | import net.minecraft.util.math.MathHelper;
10 | import net.minecraft.world.chunk.ChunkStatus;
11 | import org.jetbrains.annotations.Nullable;
12 | import org.spongepowered.asm.mixin.Final;
13 | import org.spongepowered.asm.mixin.Mixin;
14 | import org.spongepowered.asm.mixin.Shadow;
15 | import org.spongepowered.asm.mixin.Unique;
16 | import org.spongepowered.asm.mixin.injection.At;
17 | import org.spongepowered.asm.mixin.injection.Inject;
18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
19 |
20 | import java.util.Optional;
21 |
22 | @Mixin(WorldGenerationProgressTracker.class)
23 | public abstract class WorldGenerationProgressTrackerMixin implements SQWorldGenerationProgressTracker {
24 | @Shadow
25 | @Final
26 | private Long2ObjectOpenHashMap chunkStatuses;
27 |
28 | @Shadow
29 | private ChunkPos spawnPos;
30 |
31 | @Shadow
32 | @Final
33 | private int radius;
34 |
35 | @Shadow
36 | public abstract @Nullable ChunkStatus getChunkStatus(int x, int z);
37 |
38 | @Shadow
39 | @Final
40 | private int centerSize;
41 |
42 | @Unique
43 | private long freezeTime = -1;
44 |
45 | @Unique
46 | @Nullable
47 | private WorldGenerationProgressTracker frozenCopy;
48 |
49 | @Unique
50 | private volatile int progressLevel;
51 | @Unique
52 | private volatile int progressPercentage;
53 |
54 | @Inject(
55 | method = "",
56 | at = @At("TAIL")
57 | )
58 | private void markFreezeTimeStart(CallbackInfo ci) {
59 | long chunkMapFreezingTime = SeedQueue.config.chunkMapFreezing;
60 | if (chunkMapFreezingTime != -1 && this.freezeTime == -1) {
61 | this.makeFrozenCopyAfter(chunkMapFreezingTime);
62 | }
63 | }
64 |
65 | @Inject(
66 | method = "setChunkStatus",
67 | at = @At(
68 | value = "INVOKE",
69 | target = "Lnet/minecraft/server/WorldGenerationProgressLogger;setChunkStatus(Lnet/minecraft/util/math/ChunkPos;Lnet/minecraft/world/chunk/ChunkStatus;)V"
70 | )
71 | )
72 | private void onSetChunkStatus(CallbackInfo ci) {
73 | if (this.frozenCopy == null && this.seedQueue$shouldFreeze()) {
74 | this.makeFrozenCopy();
75 | }
76 | }
77 |
78 | @Inject(
79 | method = "start(Lnet/minecraft/util/math/ChunkPos;)V",
80 | at = @At("TAIL")
81 | )
82 | private void recalculateProgressCount(CallbackInfo ci) {
83 | this.progressLevel = 0;
84 | this.calculateProgressCount();
85 | }
86 |
87 | @Inject(
88 | method = "setChunkStatus",
89 | at = @At("TAIL")
90 | )
91 | private void recalculateProgressCount(ChunkPos pos, ChunkStatus status, CallbackInfo ci) {
92 | if (status == ChunkStatus.FULL) {
93 | this.calculateProgressCount();
94 | }
95 | }
96 |
97 | @Unique
98 | private void makeFrozenCopy() {
99 | WorldGenerationProgressTracker frozenCopy = new WorldGenerationProgressTracker(this.radius - ChunkStatus.getMaxTargetGenerationRadius()); // This will trigger a makeFrozenCopyAfter inside the frozen copy itself which could lead to further recursion, but as long as setChunkStatus isn't called, this will never be an issue.
100 | ((WorldGenerationProgressTrackerMixin) (Object) frozenCopy).setAsFrozenCopy(this.chunkStatuses, this.spawnPos);
101 | this.frozenCopy = frozenCopy;
102 | }
103 |
104 | @Unique
105 | private void makeFrozenCopyAfter(long millis) {
106 | this.freezeTime = Util.getMeasuringTimeMs() + millis;
107 | }
108 |
109 | @Unique
110 | private void setAsFrozenCopy(Long2ObjectOpenHashMap chunkStatuses, ChunkPos spawnPos) {
111 | this.chunkStatuses.putAll(chunkStatuses);
112 | this.spawnPos = spawnPos;
113 | }
114 |
115 | @Override
116 | public boolean seedQueue$shouldFreeze() {
117 | return this.freezeTime != -1 && Util.getMeasuringTimeMs() >= this.freezeTime;
118 | }
119 |
120 | @Override
121 | public Optional seedQueue$getFrozenCopy() {
122 | return Optional.ofNullable(this.frozenCopy);
123 | }
124 |
125 | @Unique
126 | private void calculateProgressCount() {
127 | // load cached level to avoid checking levels we already know are full
128 | int level = this.progressLevel;
129 | int count = (level * 2 - 1) * (level * 2 - 1);
130 | boolean end = false;
131 |
132 | for (; level < this.radius; level++) {
133 | // travel left to right on the x-axis
134 | // when on the right and leftmost bounds of the current level,
135 | // check all the y values of that ring,
136 | // otherwise, just check the top and bottom of the ring.
137 | // if any chunks are missing in the ring,
138 | // no future rings are searched but the current ring is completed.
139 | //
140 | //
141 | // ↑ then ->
142 | //
143 | // - - - - -
144 | // - + + + -
145 | // - + = + -
146 | // - + + + -
147 | // - - - - -
148 |
149 | // adding radius as WorldGenerationProgressTracker#getChunkStatus subtracts it
150 | int leftX = -level + this.radius;
151 | int rightX = level + this.radius;
152 | int bottomZ = -level + this.radius;
153 | int topZ = level + this.radius;
154 |
155 | for (int x = leftX; x <= rightX; ++x) {
156 | boolean onBounds = x == leftX || x == rightX;
157 | for (int z = bottomZ; z <= topZ; z += onBounds ? 1 : level * 2) {
158 | if (this.getChunkStatus(x, z) == ChunkStatus.FULL) {
159 | count++;
160 | } else {
161 | end = true;
162 | }
163 | }
164 | }
165 |
166 | if (end) {
167 | break;
168 | }
169 | }
170 |
171 | this.progressLevel = level;
172 | this.progressPercentage = MathHelper.clamp(count * 100 / (this.centerSize * this.centerSize), 0, 100);
173 | }
174 |
175 | @Override
176 | public int seedQueue$getProgressPercentage() {
177 | return this.progressPercentage;
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/client/debug/DebugHudMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.client.debug;
2 |
3 | import com.llamalad7.mixinextras.injector.ModifyReturnValue;
4 | import me.contaria.seedqueue.SeedQueue;
5 | import net.minecraft.client.gui.hud.DebugHud;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.injection.At;
8 |
9 | import java.util.List;
10 |
11 | @Mixin(DebugHud.class)
12 | public abstract class DebugHudMixin {
13 |
14 | @ModifyReturnValue(
15 | method = "getRightText",
16 | at = @At("RETURN")
17 | )
18 | private List addSeedQueueDebugText(List debugText) {
19 | if (SeedQueue.isActive()) {
20 | debugText.addAll(SeedQueue.getDebugText());
21 | }
22 | return debugText;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/client/levellist/LevelStorageMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.client.levellist;
2 |
3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
5 | import me.contaria.seedqueue.SeedQueue;
6 | import net.minecraft.world.level.storage.LevelStorage;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.injection.At;
9 |
10 | import java.io.File;
11 |
12 | @Mixin(LevelStorage.class)
13 | public abstract class LevelStorageMixin {
14 |
15 | @WrapOperation(
16 | method = "getLevelList",
17 | at = @At(
18 | value = "INVOKE",
19 | target = "Ljava/io/File;isDirectory()Z",
20 | remap = false
21 | )
22 | )
23 | private boolean reduceLevelList(File file, Operation original) {
24 | String name = file.getName();
25 | if (SeedQueue.config.reduceLevelList && (name.startsWith("Benchmark Reset #") || (name.startsWith("Random Speedrun #") || name.startsWith("Set Speedrun #")) && !new File(file, "level.dat_old").exists())) {
26 | return false;
27 | }
28 | return original.call(file);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/client/profiling/WorldRendererMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.client.profiling;
2 |
3 | import me.contaria.seedqueue.debug.SeedQueueProfiler;
4 | import net.minecraft.client.render.WorldRenderer;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.injection.At;
7 | import org.spongepowered.asm.mixin.injection.Inject;
8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
9 |
10 | /**
11 | * Profiling mixins add more usage of the profiler to hot paths during wall rendering.
12 | * These Mixins will be removed in later versions of SeedQueue.
13 | */
14 | @Mixin(value = WorldRenderer.class, priority = 500)
15 | public abstract class WorldRendererMixin {
16 |
17 | @Inject(
18 | method = "setWorld",
19 | at = @At("HEAD")
20 | )
21 | private void profileVanilla_setWorld(CallbackInfo ci) {
22 | SeedQueueProfiler.push("vanilla");
23 | }
24 |
25 | @Inject(
26 | method = "setWorld",
27 | at = @At(
28 | value = "INVOKE",
29 | target = "Lnet/minecraft/client/render/WorldRenderer;reload()V"
30 | )
31 | )
32 | private void profileReload(CallbackInfo ci) {
33 | SeedQueueProfiler.push("reload");
34 | }
35 |
36 | @Inject(
37 | method = "setWorld",
38 | at = @At(
39 | value = "INVOKE",
40 | target = "Ljava/util/Set;clear()V",
41 | ordinal = 0
42 | )
43 | )
44 | private void profileClearChunks(CallbackInfo ci) {
45 | SeedQueueProfiler.push("clear_chunks");
46 | }
47 |
48 | @Inject(
49 | method = "setWorld",
50 | at = @At(
51 | value = "INVOKE",
52 | target = "Lnet/minecraft/client/render/chunk/ChunkBuilder;stop()V"
53 | )
54 | )
55 | private void profileStopBuilder(CallbackInfo ci) {
56 | SeedQueueProfiler.swap("stop_builder");
57 | }
58 |
59 | @Inject(
60 | method = "setWorld",
61 | at = @At(
62 | value = "FIELD",
63 | target = "Lnet/minecraft/client/render/WorldRenderer;noCullingBlockEntities:Ljava/util/Set;"
64 | )
65 | )
66 | private void profileClearBlockEntities(CallbackInfo ci) {
67 | SeedQueueProfiler.swap("clear_block_entities");
68 | }
69 |
70 | @Inject(
71 | method = "setWorld",
72 | at = @At("TAIL")
73 | )
74 | private void profilePop_setWorld(CallbackInfo ci) {
75 | SeedQueueProfiler.pop();
76 | SeedQueueProfiler.pop();
77 | }
78 |
79 | @Inject(
80 | method = "reload",
81 | at = @At("HEAD")
82 | )
83 | private void profileTransparencyShader(CallbackInfo ci) {
84 | SeedQueueProfiler.push("transparency_shader");
85 | }
86 |
87 | @Inject(
88 | method = "reload",
89 | at = @At(
90 | value = "INVOKE",
91 | target = "Lnet/minecraft/client/world/ClientWorld;reloadColor()V"
92 | )
93 | )
94 | private void profileReloadColor(CallbackInfo ci) {
95 | SeedQueueProfiler.swap("reload_color");
96 | }
97 |
98 | @Inject(
99 | method = "reload",
100 | at = @At(
101 | value = "FIELD",
102 | target = "Lnet/minecraft/client/render/WorldRenderer;chunkBuilder:Lnet/minecraft/client/render/chunk/ChunkBuilder;",
103 | ordinal = 0
104 | )
105 | )
106 | private void profileChunkBuilder(CallbackInfo ci) {
107 | SeedQueueProfiler.swap("chunk_builder");
108 | }
109 |
110 | @Inject(
111 | method = "reload",
112 | at = @At(
113 | value = "INVOKE",
114 | target = "Lnet/minecraft/client/render/BuiltChunkStorage;clear()V"
115 | )
116 | )
117 | private void profileClearChunks_reload(CallbackInfo ci) {
118 | SeedQueueProfiler.swap("clear_chunks");
119 | }
120 |
121 | @Inject(
122 | method = "reload",
123 | at = @At(
124 | value = "INVOKE",
125 | target = "Lnet/minecraft/client/render/WorldRenderer;clearChunkRenderers()V"
126 | )
127 | )
128 | private void profileClearChunkRenderers_reload(CallbackInfo ci) {
129 | SeedQueueProfiler.swap("clear_chunk_renderers");
130 | }
131 |
132 | @Inject(
133 | method = "reload",
134 | at = @At(
135 | value = "FIELD",
136 | target = "Lnet/minecraft/client/render/WorldRenderer;noCullingBlockEntities:Ljava/util/Set;",
137 | ordinal = 0
138 | )
139 | )
140 | private void profileClearBlockEntities_reload(CallbackInfo ci) {
141 | SeedQueueProfiler.swap("clear_block_entities");
142 | }
143 |
144 | @Inject(
145 | method = "reload",
146 | at = @At(
147 | value = "NEW",
148 | target = "(Lnet/minecraft/client/render/chunk/ChunkBuilder;Lnet/minecraft/world/World;ILnet/minecraft/client/render/WorldRenderer;)Lnet/minecraft/client/render/BuiltChunkStorage;"
149 | )
150 | )
151 | private void profileBuiltChunkStorage(CallbackInfo ci) {
152 | SeedQueueProfiler.swap("built_chunk_storage");
153 | }
154 |
155 | @Inject(
156 | method = "reload",
157 | at = @At(
158 | value = "INVOKE",
159 | target = "Lnet/minecraft/client/render/BuiltChunkStorage;updateCameraPosition(DD)V"
160 | )
161 | )
162 | private void profileUpdateCameraPos(CallbackInfo ci) {
163 | SeedQueueProfiler.swap("update_camera_pos");
164 | }
165 |
166 | @Inject(
167 | method = "reload",
168 | at = @At("RETURN")
169 | )
170 | private void profilePop_reload(CallbackInfo ci) {
171 | SeedQueueProfiler.pop();
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/client/render/LevelLoadingScreenMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.client.render;
2 |
3 | import com.llamalad7.mixinextras.sugar.Share;
4 | import com.llamalad7.mixinextras.sugar.ref.LocalIntRef;
5 | import me.contaria.seedqueue.SeedQueue;
6 | import net.minecraft.client.gui.WorldGenerationProgressTracker;
7 | import net.minecraft.client.gui.screen.LevelLoadingScreen;
8 | import net.minecraft.client.util.math.MatrixStack;
9 | import org.spongepowered.asm.mixin.Dynamic;
10 | import org.spongepowered.asm.mixin.Mixin;
11 | import org.spongepowered.asm.mixin.Unique;
12 | import org.spongepowered.asm.mixin.injection.*;
13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
14 |
15 | import java.awt.*;
16 |
17 | @Mixin(value = LevelLoadingScreen.class, priority = 1500)
18 | public abstract class LevelLoadingScreenMixin {
19 |
20 | @Unique
21 | private static final int NO_MODIFIER = new Color(255, 255, 255, 255).getRGB();
22 |
23 | @Unique
24 | private static final int TRANSPARENT_MODIFIER = new Color(255, 255, 255, 150).getRGB();
25 |
26 | @Inject(
27 | method = "drawChunkMap",
28 | at = @At("HEAD")
29 | )
30 | private static void setColorModifier(MatrixStack matrixStack, WorldGenerationProgressTracker tracker, int i, int j, int k, int l, CallbackInfo ci, @Share("colorModifier") LocalIntRef colorModifier) {
31 | if (!SeedQueue.isOnWall() && SeedQueue.hasEntryMatching(entry -> tracker == entry.getWorldGenerationProgressTracker())) {
32 | colorModifier.set(TRANSPARENT_MODIFIER);
33 | } else {
34 | colorModifier.set(NO_MODIFIER);
35 | }
36 | }
37 |
38 | // This group is here for compatibility with sodium's MixinLevelLoadingScreen @Overwrite of drawChunkMap.
39 | @Group(name = "transparent")
40 | @ModifyArg(
41 | method = "drawChunkMap",
42 | at = @At(
43 | value = "INVOKE",
44 | target = "Lnet/minecraft/client/gui/screen/LevelLoadingScreen;fill(Lnet/minecraft/client/util/math/MatrixStack;IIIII)V"
45 | ),
46 | slice = @Slice(
47 | from = @At(
48 | value = "FIELD",
49 | target = "Lnet/minecraft/client/gui/screen/LevelLoadingScreen;STATUS_TO_COLOR:Lit/unimi/dsi/fastutil/objects/Object2IntMap;"
50 | )
51 | ),
52 | index = 5
53 | )
54 | private static int transparentSeedQueueChunkMap(int color, @Share("colorModifier") LocalIntRef colorModifier) {
55 | return color & colorModifier.get();
56 | }
57 |
58 | @Dynamic
59 | @Group(name = "transparent")
60 | @ModifyArg(
61 | method = "drawChunkMap",
62 | at = @At(
63 | value = "INVOKE",
64 | target = "Lnet/minecraft/client/gui/screen/LevelLoadingScreen;addRect(Lnet/minecraft/util/math/Matrix4f;Lme/jellysquid/mods/sodium/client/model/vertex/formats/screen_quad/BasicScreenQuadVertexSink;IIIII)V"
65 | ),
66 | index = 6
67 | )
68 | private static int transparentSeedQueueChunkMap_sodium(int color, @Share("colorModifier") LocalIntRef colorModifier) {
69 | return color & colorModifier.get();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/client/render/MinecraftClientMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.client.render;
2 |
3 | import me.contaria.seedqueue.SeedQueue;
4 | import me.contaria.seedqueue.SeedQueueEntry;
5 | import net.minecraft.client.MinecraftClient;
6 | import net.minecraft.client.gui.WorldGenerationProgressTracker;
7 | import net.minecraft.client.gui.screen.LevelLoadingScreen;
8 | import net.minecraft.client.util.Window;
9 | import net.minecraft.client.util.math.MatrixStack;
10 | import org.spongepowered.asm.mixin.Final;
11 | import org.spongepowered.asm.mixin.Mixin;
12 | import org.spongepowered.asm.mixin.Shadow;
13 | import org.spongepowered.asm.mixin.injection.At;
14 | import org.spongepowered.asm.mixin.injection.Inject;
15 | import org.spongepowered.asm.mixin.injection.Slice;
16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
17 |
18 | @Mixin(MinecraftClient.class)
19 | public abstract class MinecraftClientMixin {
20 |
21 | @Shadow
22 | @Final
23 | private Window window;
24 |
25 | @Inject(
26 | method = "render",
27 | at = @At(
28 | value = "INVOKE",
29 | target = "Lnet/minecraft/util/profiler/Profiler;pop()V",
30 | ordinal = 0
31 | ),
32 | slice = @Slice(
33 | from = @At(
34 | value = "INVOKE",
35 | target = "Lnet/minecraft/client/render/GameRenderer;render(FJZ)V"
36 | )
37 | )
38 | )
39 | private void drawSeedQueueChunkMaps(CallbackInfo ci) {
40 | if (SeedQueue.isOnWall() || !SeedQueue.config.showChunkMaps) {
41 | return;
42 | }
43 |
44 | int x = 3;
45 | int y = 3;
46 | for (SeedQueueEntry seedQueueEntry : SeedQueue.getEntries()) {
47 | if (seedQueueEntry.isPaused()) {
48 | continue;
49 | }
50 | WorldGenerationProgressTracker tracker = seedQueueEntry.getWorldGenerationProgressTracker();
51 | if (tracker == null) {
52 | continue;
53 | }
54 | if (x + tracker.getSize() > this.window.getScaledWidth() - 3) {
55 | x = 3;
56 | y += tracker.getSize() + 3;
57 | }
58 | LevelLoadingScreen.drawChunkMap(new MatrixStack(), tracker, x + tracker.getSize() / 2, y + tracker.getSize() / 2, 1, 0);
59 | x += tracker.getSize() + 3;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/client/sounds/SoundManagerMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.client.sounds;
2 |
3 | import me.contaria.seedqueue.interfaces.SQSoundManager;
4 | import me.contaria.seedqueue.interfaces.SQSoundSystem;
5 | import net.minecraft.client.sound.SoundManager;
6 | import net.minecraft.client.sound.SoundSystem;
7 | import org.spongepowered.asm.mixin.Final;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.Shadow;
10 |
11 | @Mixin(SoundManager.class)
12 | public abstract class SoundManagerMixin implements SQSoundManager {
13 | @Shadow
14 | @Final
15 | private SoundSystem soundSystem;
16 |
17 | @Override
18 | public void seedQueue$stopAllExceptSeedQueueSounds() {
19 | ((SQSoundSystem) this.soundSystem).seedQueue$stopAllExceptSeedQueueSounds();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/client/sounds/SoundSystemMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.client.sounds;
2 |
3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
4 | import me.contaria.seedqueue.interfaces.SQSoundSystem;
5 | import net.minecraft.client.sound.AudioStream;
6 | import net.minecraft.client.sound.Channel;
7 | import net.minecraft.client.sound.SoundInstance;
8 | import net.minecraft.client.sound.SoundSystem;
9 | import org.spongepowered.asm.mixin.Final;
10 | import org.spongepowered.asm.mixin.Mixin;
11 | import org.spongepowered.asm.mixin.Shadow;
12 | import org.spongepowered.asm.mixin.injection.At;
13 |
14 | import java.util.Map;
15 | import java.util.concurrent.CompletableFuture;
16 |
17 | @Mixin(SoundSystem.class)
18 | public abstract class SoundSystemMixin implements SQSoundSystem {
19 | @Shadow
20 | @Final
21 | private Map sources;
22 |
23 | @Shadow
24 | public abstract void stop(SoundInstance soundInstance);
25 |
26 | @ModifyExpressionValue(
27 | method = "play(Lnet/minecraft/client/sound/SoundInstance;)V",
28 | at = {
29 | @At(
30 | value = "INVOKE",
31 | target = "Lnet/minecraft/client/sound/SoundLoader;loadStatic(Lnet/minecraft/util/Identifier;)Ljava/util/concurrent/CompletableFuture;"
32 | ),
33 | @At(
34 | value = "INVOKE",
35 | target = "Lnet/minecraft/client/sound/SoundLoader;loadStreamed(Lnet/minecraft/util/Identifier;Z)Ljava/util/concurrent/CompletableFuture;"
36 | )
37 | }
38 | )
39 | private CompletableFuture crashBrokenSeedQueueSoundFiles(CompletableFuture audio, SoundInstance soundInstance) {
40 | if (soundInstance.getId().getNamespace().equals("seedqueue") && audio.isCompletedExceptionally()) {
41 | throw new RuntimeException("Tried to play a broken sound file from a SeedQueue customization pack (\"" + soundInstance.getId() + "\")! If you are using empty sound files to mute sounds on the wall screen, use short silent ones instead.");
42 | }
43 | return audio;
44 | }
45 |
46 | // see SoundSystem#closeSounds
47 | @Override
48 | public void seedQueue$stopAllExceptSeedQueueSounds() {
49 | for (SoundInstance sound : this.sources.keySet()) {
50 | if (!sound.getId().getNamespace().equals("seedqueue")) {
51 | this.stop(sound);
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/atum/AttemptTrackerMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.atum;
2 |
3 | import me.contaria.seedqueue.SeedQueue;
4 | import me.voidxwalker.autoreset.AttemptTracker;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.Shadow;
7 | import org.spongepowered.asm.mixin.injection.At;
8 | import org.spongepowered.asm.mixin.injection.Inject;
9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
10 |
11 | import java.io.IOException;
12 |
13 | @Mixin(value = AttemptTracker.class, remap = false)
14 | public abstract class AttemptTrackerMixin {
15 |
16 | @Shadow
17 | public abstract void register(AttemptTracker.Type type) throws IOException;
18 |
19 | @Inject(
20 | method = "",
21 | at = @At("TAIL")
22 | )
23 | private void registerBenchmarkResetCounter(CallbackInfo ci) throws IOException {
24 | this.register(SeedQueue.BENCHMARK_RESETS);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/atum/AtumMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.atum;
2 |
3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
5 | import me.contaria.seedqueue.SeedQueue;
6 | import me.contaria.seedqueue.SeedQueueEntry;
7 | import me.contaria.seedqueue.compat.ModCompat;
8 | import me.contaria.seedqueue.gui.wall.SeedQueueWallScreen;
9 | import me.contaria.seedqueue.sounds.SeedQueueSounds;
10 | import me.voidxwalker.autoreset.Atum;
11 | import net.minecraft.client.MinecraftClient;
12 | import net.minecraft.client.gui.screen.ProgressScreen;
13 | import net.minecraft.client.gui.screen.Screen;
14 | import org.spongepowered.asm.mixin.Mixin;
15 | import org.spongepowered.asm.mixin.injection.At;
16 | import org.spongepowered.asm.mixin.injection.Inject;
17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
18 |
19 | import java.util.Optional;
20 |
21 | @Mixin(value = Atum.class, remap = false)
22 | public abstract class AtumMixin {
23 |
24 | @WrapOperation(
25 | method = "createNewWorld",
26 | at = @At(
27 | value = "INVOKE",
28 | target = "Lnet/minecraft/client/MinecraftClient;openScreen(Lnet/minecraft/client/gui/screen/Screen;)V",
29 | remap = true
30 | )
31 | )
32 | private static void openSeedQueueWallScreen(MinecraftClient client, Screen screen, Operation original) {
33 | if (!SeedQueue.isActive()) {
34 | original.call(client, screen);
35 | return;
36 | }
37 | if (SeedQueue.config.shouldUseWall()) {
38 | if (SeedQueue.config.bypassWall) {
39 | Optional nextSeedQueueEntry = SeedQueue.getEntryMatching(entry -> entry.isReady() && entry.isLocked());
40 | if (nextSeedQueueEntry.isPresent()) {
41 | SeedQueueSounds.play(SeedQueueSounds.BYPASS_WALL);
42 | SeedQueue.playEntry(nextSeedQueueEntry.get());
43 | return;
44 | }
45 | }
46 | // standardsettings can cause the current screen to be re-initialized,
47 | // so we open an intermission screen to avoid atum reset logic being called twice
48 | client.openScreen(new ProgressScreen());
49 | ModCompat.standardsettings$cache();
50 | ModCompat.standardsettings$reset();
51 | ModCompat.stateoutput$setWallState();
52 | SeedQueueSounds.play(SeedQueueSounds.OPEN_WALL);
53 | client.openScreen(new SeedQueueWallScreen());
54 | return;
55 | }
56 | if (!SeedQueue.playEntry()) {
57 | original.call(client, screen);
58 | }
59 | }
60 |
61 | @Inject(
62 | method = "stopRunning",
63 | at = @At("TAIL")
64 | )
65 | private static void stopSeedQueueOnAtumStop(CallbackInfo ci) {
66 | SeedQueue.stop();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/atum/CreateWorldScreenMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.atum;
2 |
3 | import com.bawnorton.mixinsquared.TargetHandler;
4 | import me.contaria.seedqueue.SeedQueue;
5 | import me.voidxwalker.autoreset.AttemptTracker;
6 | import net.minecraft.client.gui.screen.world.CreateWorldScreen;
7 | import org.spongepowered.asm.mixin.Dynamic;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.injection.At;
10 | import org.spongepowered.asm.mixin.injection.ModifyArg;
11 |
12 | @Mixin(value = CreateWorldScreen.class, priority = 1500)
13 | public abstract class CreateWorldScreenMixin {
14 |
15 | @Dynamic
16 | @TargetHandler(
17 | mixin = "me.voidxwalker.autoreset.mixin.config.CreateWorldScreenMixin",
18 | name = "createWorld"
19 | )
20 | @ModifyArg(
21 | method = "@MixinSquared:Handler",
22 | at = @At(
23 | value = "INVOKE",
24 | target = "Lme/voidxwalker/autoreset/AttemptTracker;incrementAndGetWorldName(Lme/voidxwalker/autoreset/AttemptTracker$Type;)Ljava/lang/String;",
25 | remap = false
26 | )
27 | )
28 | private AttemptTracker.Type useBenchmarkResetCounter(AttemptTracker.Type type) {
29 | if (SeedQueue.isBenchmarking()) {
30 | return SeedQueue.BENCHMARK_RESETS;
31 | }
32 | return type;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/atum/KeyboardMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.atum;
2 |
3 | import com.bawnorton.mixinsquared.TargetHandler;
4 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
5 | import me.contaria.seedqueue.SeedQueue;
6 | import me.contaria.seedqueue.gui.config.SeedQueueKeybindingsScreen;
7 | import net.minecraft.client.Keyboard;
8 | import net.minecraft.client.MinecraftClient;
9 | import org.spongepowered.asm.mixin.Dynamic;
10 | import org.spongepowered.asm.mixin.Final;
11 | import org.spongepowered.asm.mixin.Mixin;
12 | import org.spongepowered.asm.mixin.Shadow;
13 | import org.spongepowered.asm.mixin.injection.At;
14 |
15 | @Mixin(value = Keyboard.class, priority = 1500)
16 | public abstract class KeyboardMixin {
17 |
18 | @Shadow
19 | @Final
20 | private MinecraftClient client;
21 |
22 | @Dynamic
23 | @TargetHandler(
24 | mixin = "me.voidxwalker.autoreset.mixin.hotkey.KeyboardMixin",
25 | name = "onKey"
26 | )
27 | @ModifyExpressionValue(
28 | method = "@MixinSquared:Handler",
29 | at = @At(
30 | value = "INVOKE",
31 | target = "Lnet/minecraft/client/options/KeyBinding;matchesKey(II)Z"
32 | )
33 | )
34 | private boolean doNotActivateAtumHotKey_onWall(boolean matchesKey) {
35 | return matchesKey && !(SeedQueue.isOnWall() || this.client.currentScreen instanceof SeedQueueKeybindingsScreen);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/sodium/ChunkBuilder$WorkerRunnableMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.sodium;
2 |
3 | import me.contaria.seedqueue.SeedQueue;
4 | import me.contaria.seedqueue.compat.SodiumCompat;
5 | import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers;
6 | import org.spongepowered.asm.mixin.Final;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.Shadow;
9 | import org.spongepowered.asm.mixin.injection.At;
10 | import org.spongepowered.asm.mixin.injection.Inject;
11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
12 |
13 | @Mixin(targets = "me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder$WorkerRunnable", remap = false)
14 | public abstract class ChunkBuilder$WorkerRunnableMixin {
15 |
16 | @Shadow
17 | @Final
18 | private ChunkBuildBuffers bufferCache;
19 |
20 | @Inject(
21 | method = "run",
22 | at = @At("RETURN")
23 | )
24 | private void cacheBuildBuffersOnWall(CallbackInfo ci) {
25 | if (SeedQueue.isOnWall()) {
26 | SodiumCompat.WALL_BUILD_BUFFERS_POOL.add(this.bufferCache);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/sodium/ChunkBuilder$WorkerRunnableMixin2.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.sodium;
2 |
3 | import me.contaria.seedqueue.interfaces.sodium.SQChunkBuilder$WorkerRunnable;
4 | import me.jellysquid.mods.sodium.client.render.pipeline.context.ChunkRenderCacheLocal;
5 | import net.minecraft.client.MinecraftClient;
6 | import net.minecraft.world.World;
7 | import org.spongepowered.asm.mixin.*;
8 | import org.spongepowered.asm.mixin.injection.At;
9 | import org.spongepowered.asm.mixin.injection.Inject;
10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
11 |
12 | @Mixin(targets = "me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder$WorkerRunnable", remap = false)
13 | public abstract class ChunkBuilder$WorkerRunnableMixin2 implements SQChunkBuilder$WorkerRunnable {
14 |
15 | @Mutable
16 | @Shadow
17 | @Final
18 | private ChunkRenderCacheLocal cache;
19 |
20 | @Unique
21 | private World world;
22 |
23 | @Inject(
24 | method = "run",
25 | at = @At("HEAD")
26 | )
27 | private void createRenderCacheOnWorkerThread(CallbackInfo ci) {
28 | if (this.cache == null) {
29 | this.cache = new ChunkRenderCacheLocal(MinecraftClient.getInstance(), this.world);
30 | }
31 | this.world = null;
32 | }
33 |
34 | @Override
35 | public void seedQueue$setWorldForRenderCache(World world) {
36 | if (this.cache == null) {
37 | this.world = world;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/sodium/ChunkBuilderMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.sodium;
2 |
3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
4 | import com.llamalad7.mixinextras.injector.ModifyReturnValue;
5 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
6 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
7 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
8 | import me.contaria.seedqueue.SeedQueue;
9 | import me.contaria.seedqueue.compat.SodiumCompat;
10 | import me.contaria.seedqueue.interfaces.sodium.SQChunkBuilder$WorkerRunnable;
11 | import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers;
12 | import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuilder;
13 | import me.jellysquid.mods.sodium.client.render.chunk.passes.BlockRenderPassManager;
14 | import me.jellysquid.mods.sodium.client.render.pipeline.context.ChunkRenderCacheLocal;
15 | import net.minecraft.client.MinecraftClient;
16 | import net.minecraft.world.World;
17 | import org.apache.logging.log4j.Logger;
18 | import org.spongepowered.asm.mixin.*;
19 | import org.spongepowered.asm.mixin.injection.*;
20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
21 |
22 | import java.util.Objects;
23 | import java.util.concurrent.atomic.AtomicBoolean;
24 |
25 | @Mixin(value = ChunkBuilder.class, remap = false)
26 | public abstract class ChunkBuilderMixin {
27 |
28 | @Shadow
29 | private World world;
30 |
31 | @Mutable
32 | @Shadow
33 | @Final
34 | private AtomicBoolean running;
35 |
36 | @ModifyReturnValue(
37 | method = "getMaxThreadCount",
38 | at = @At("RETURN")
39 | )
40 | private static int modifyMaxThreads(int maxThreads) {
41 | if (SeedQueue.isOnWall()) {
42 | return SeedQueue.config.getChunkUpdateThreads();
43 | }
44 | return maxThreads;
45 | }
46 |
47 | @ModifyArg(
48 | method = "createWorker",
49 | at = @At(
50 | value = "INVOKE",
51 | target = "Ljava/lang/Thread;setPriority(I)V"
52 | )
53 | )
54 | private int modifyChunkUpdateThreadPriority(int priority) {
55 | if (SeedQueue.isOnWall()) {
56 | return SeedQueue.config.chunkUpdateThreadPriority;
57 | }
58 | return priority;
59 | }
60 |
61 | @WrapWithCondition(
62 | method = "stopWorkers",
63 | at = @At(
64 | value = "INVOKE",
65 | target = "Ljava/lang/Thread;join()V"
66 | )
67 | )
68 | private boolean doNotWaitForWorkersToStopOnWall(Thread thread) {
69 | return !SeedQueue.isOnWall();
70 | }
71 |
72 | // because ChunkBuilderMixin#doNotWaitForWorkersToStopOnWall prevents waiting for old workers to shut down,
73 | // it's necessary to replace the atomic boolean to prevent the worker threads staying alive
74 | // when new workers are started before old ones have stopped
75 | @Inject(
76 | method = "stopWorkers",
77 | at = @At("TAIL")
78 | )
79 | private void replaceRunningAtomicBooleanOnWall(CallbackInfo ci) {
80 | if (SeedQueue.isOnWall()) {
81 | this.running = new AtomicBoolean();
82 | }
83 | }
84 |
85 | // mac sodium compat is very silly
86 | @Group(name = "loadCachedBuildBuffersOnWall")
87 | @WrapOperation(
88 | method = "createWorker",
89 | at = @At(
90 | value = "NEW",
91 | target = "(Lme/jellysquid/mods/sodium/client/model/vertex/type/ChunkVertexType;Lme/jellysquid/mods/sodium/client/render/chunk/passes/BlockRenderPassManager;)Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers;"
92 | )
93 | )
94 | private ChunkBuildBuffers loadCachedBuildBuffersOnWall(@Coerce Object passId, BlockRenderPassManager buffers, Operation original) {
95 | if (SeedQueue.isOnWall() && !SodiumCompat.WALL_BUILD_BUFFERS_POOL.isEmpty()) {
96 | return Objects.requireNonNull(SodiumCompat.WALL_BUILD_BUFFERS_POOL.remove(0));
97 | }
98 | return original.call(passId, buffers);
99 | }
100 |
101 | @Dynamic
102 | @Group(name = "loadCachedBuildBuffersOnWall", min = 1, max = 1)
103 | @WrapOperation(
104 | method = "createWorker",
105 | at = @At(
106 | value = "NEW",
107 | target = "(Lme/jellysquid/mods/sodium/client/gl/attribute/GlVertexFormat;Lme/jellysquid/mods/sodium/client/render/chunk/passes/BlockRenderPassManager;)Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers;"
108 | )
109 | )
110 | private ChunkBuildBuffers loadCachedBuildBuffersOnWall_macSodium(@Coerce Object format, BlockRenderPassManager buffers, Operation original) {
111 | if (SeedQueue.isOnWall() && !SodiumCompat.WALL_BUILD_BUFFERS_POOL.isEmpty()) {
112 | return Objects.requireNonNull(SodiumCompat.WALL_BUILD_BUFFERS_POOL.remove(0));
113 | }
114 | return original.call(format, buffers);
115 | }
116 |
117 | @WrapOperation(
118 | method = "createWorker",
119 | at = @At(
120 | value = "NEW",
121 | target = "(Lnet/minecraft/client/MinecraftClient;Lnet/minecraft/world/World;)Lme/jellysquid/mods/sodium/client/render/pipeline/context/ChunkRenderCacheLocal;",
122 | remap = true
123 | ),
124 | require = 0
125 | )
126 | private ChunkRenderCacheLocal createRenderCacheOnWorkerThread(MinecraftClient client, World world, Operation original) {
127 | if (SeedQueue.isOnWall()) {
128 | return null;
129 | }
130 | return original.call(client, world);
131 | }
132 |
133 | @SuppressWarnings("InvalidInjectorMethodSignature") // MCDev doesn't seem to like @Coerce on the return type
134 | @ModifyExpressionValue(
135 | method = "createWorker",
136 | at = @At(
137 | value = "NEW",
138 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder$WorkerRunnable;"
139 | )
140 | )
141 | private @Coerce Object passWorldToWorkerThread(@Coerce Object worker) {
142 | if (worker instanceof SQChunkBuilder$WorkerRunnable) {
143 | ((SQChunkBuilder$WorkerRunnable) worker).seedQueue$setWorldForRenderCache(this.world);
144 | }
145 | return worker;
146 | }
147 |
148 | @WrapWithCondition(
149 | method = "startWorkers",
150 | at = @At(
151 | value = "INVOKE",
152 | target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;)V"
153 | )
154 | )
155 | private boolean suppressStartWorkersLogOnWall(Logger logger, String s, Object o) {
156 | return !SeedQueue.isOnWall();
157 | }
158 |
159 | @WrapWithCondition(
160 | method = "stopWorkers",
161 | at = @At(
162 | value = "INVOKE",
163 | target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;)V"
164 | )
165 | )
166 | private boolean suppressStopWorkersLogOnWall(Logger logger, String s) {
167 | return !SeedQueue.isOnWall();
168 | }
169 |
170 | @ModifyExpressionValue(
171 | method = "getSchedulingBudget",
172 | at = @At(
173 | value = "CONSTANT",
174 | args = "intValue=2")
175 | )
176 | private int reduceSchedulingBudgetOnWall(int TASK_QUEUE_LIMIT_PER_WORKER) {
177 | if (SeedQueue.isOnWall()) {
178 | return 1;
179 | }
180 | return TASK_QUEUE_LIMIT_PER_WORKER;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/sodium/ChunkRenderManagerMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.sodium;
2 |
3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
4 | import me.contaria.seedqueue.SeedQueue;
5 | import me.jellysquid.mods.sodium.client.render.chunk.ChunkRenderManager;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.injection.At;
8 |
9 | @Mixin(value = ChunkRenderManager.class, remap = false)
10 | public abstract class ChunkRenderManagerMixin {
11 |
12 | @ModifyExpressionValue(
13 | method = "isChunkPrioritized",
14 | at = @At(
15 | value = "FIELD",
16 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager;NEARBY_CHUNK_DISTANCE:D"
17 | )
18 | )
19 | private double decreaseNearbyChunkDistanceOnWall(double nearbyChunkDistance) {
20 | if (SeedQueue.isOnWall()) {
21 | return 16 * 16;
22 | }
23 | return nearbyChunkDistance;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/sodium/ChunkRenderShaderBackendMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.sodium;
2 |
3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
4 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
5 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
6 | import me.contaria.seedqueue.SeedQueue;
7 | import me.contaria.seedqueue.compat.SodiumCompat;
8 | import me.jellysquid.mods.sodium.client.gl.attribute.GlVertexFormat;
9 | import me.jellysquid.mods.sodium.client.render.chunk.format.ChunkMeshAttribute;
10 | import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkFogMode;
11 | import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkProgram;
12 | import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkRenderShaderBackend;
13 | import org.spongepowered.asm.mixin.Dynamic;
14 | import org.spongepowered.asm.mixin.Mixin;
15 | import org.spongepowered.asm.mixin.injection.At;
16 | import org.spongepowered.asm.mixin.injection.Coerce;
17 | import org.spongepowered.asm.mixin.injection.Group;
18 |
19 | @Mixin(value = ChunkRenderShaderBackend.class, remap = false)
20 | public abstract class ChunkRenderShaderBackendMixin {
21 |
22 | @Group(name = "cacheShaders")
23 | @WrapOperation(
24 | method = "createShaders",
25 | at = @At(
26 | value = "INVOKE",
27 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkRenderShaderBackend;createShader(Lme/jellysquid/mods/sodium/client/gl/device/RenderDevice;Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkFogMode;Lme/jellysquid/mods/sodium/client/gl/attribute/GlVertexFormat;)Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkProgram;"
28 | )
29 | )
30 | private ChunkProgram cacheShadersOnWall(ChunkRenderShaderBackend> instance, @Coerce Object device, ChunkFogMode fogMode, GlVertexFormat vertexFormat, Operation original) {
31 | if (SeedQueue.isOnWall()) {
32 | return SodiumCompat.WALL_SHADER_CACHE.computeIfAbsent(fogMode, f -> original.call(instance, device, fogMode, vertexFormat));
33 | }
34 | return original.call(instance, device, fogMode, vertexFormat);
35 | }
36 |
37 | @Dynamic
38 | @Group(name = "cacheShaders")
39 | @WrapOperation(
40 | method = "createShaders",
41 | at = @At(
42 | value = "INVOKE",
43 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkRenderShaderBackend;createShader(Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkFogMode;Lme/jellysquid/mods/sodium/client/gl/attribute/GlVertexFormat;)Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkProgram;"
44 | )
45 | )
46 | private ChunkProgram cacheShadersOnWall_macSodium(ChunkRenderShaderBackend> instance, ChunkFogMode fogMode, GlVertexFormat vertexFormat, Operation original) {
47 | if (SeedQueue.isOnWall()) {
48 | return SodiumCompat.WALL_SHADER_CACHE.computeIfAbsent(fogMode, f -> original.call(instance, fogMode, vertexFormat));
49 | }
50 | return original.call(instance, fogMode, vertexFormat);
51 | }
52 |
53 | @WrapWithCondition(
54 | method = "delete",
55 | at = @At(
56 | value = "INVOKE",
57 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/shader/ChunkProgram;delete()V"
58 | )
59 | )
60 | private boolean doNotDeleteCachedShaders(ChunkProgram program) {
61 | return !SodiumCompat.WALL_SHADER_CACHE.containsValue(program);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/sodium/profiling/ChunkBuilderMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.sodium.profiling;
2 |
3 | import me.contaria.seedqueue.debug.SeedQueueProfiler;
4 | import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuilder;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.injection.At;
7 | import org.spongepowered.asm.mixin.injection.Inject;
8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
9 |
10 | /**
11 | * Profiling mixins add more usage of the profiler to hot paths during wall rendering.
12 | * These Mixins will be removed in later versions of SeedQueue.
13 | */
14 | @Mixin(value = ChunkBuilder.class, remap = false)
15 | public abstract class ChunkBuilderMixin {
16 |
17 | @Inject(
18 | method = "startWorkers",
19 | at = @At(
20 | value = "INVOKE",
21 | target = "Lnet/minecraft/client/MinecraftClient;getInstance()Lnet/minecraft/client/MinecraftClient;",
22 | remap = true
23 | )
24 | )
25 | private void profileStartWorkers(CallbackInfo ci) {
26 | SeedQueueProfiler.push("start_workers");
27 | }
28 |
29 | @Inject(
30 | method = "createWorker",
31 | at = @At(
32 | value = "INVOKE",
33 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers;(Lme/jellysquid/mods/sodium/client/model/vertex/type/ChunkVertexType;Lme/jellysquid/mods/sodium/client/render/chunk/passes/BlockRenderPassManager;)V"
34 | )
35 | )
36 | private void profileBuildBuffers(CallbackInfo ci) {
37 | SeedQueueProfiler.push("build_buffers");
38 | }
39 |
40 | @Inject(
41 | method = "createWorker",
42 | at = @At(
43 | value = "INVOKE",
44 | target = "Lme/jellysquid/mods/sodium/client/render/pipeline/context/ChunkRenderCacheLocal;(Lnet/minecraft/client/MinecraftClient;Lnet/minecraft/world/World;)V",
45 | remap = true
46 | )
47 | )
48 | private void profileRenderCache(CallbackInfo ci) {
49 | SeedQueueProfiler.swap("render_cache");
50 | }
51 |
52 | @Inject(
53 | method = "createWorker",
54 | at = @At(
55 | value = "INVOKE",
56 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder$WorkerRunnable;(Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder;Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers;Lme/jellysquid/mods/sodium/client/render/pipeline/context/ChunkRenderCacheLocal;)V"
57 | )
58 | )
59 | private void profileWorker(CallbackInfo ci) {
60 | SeedQueueProfiler.swap("worker");
61 | }
62 |
63 | @Inject(
64 | method = "createWorker",
65 | at = @At(
66 | value = "INVOKE",
67 | target = "Ljava/lang/Thread;(Ljava/lang/Runnable;Ljava/lang/String;)V"
68 | )
69 | )
70 | private void profileThread(CallbackInfo ci) {
71 | SeedQueueProfiler.swap("thread");
72 | }
73 |
74 | @Inject(
75 | method = "createWorker",
76 | at = @At(
77 | value = "INVOKE",
78 | target = "Ljava/util/List;add(Ljava/lang/Object;)Z"
79 | )
80 | )
81 | private void profilePopPerWorker(CallbackInfo ci) {
82 | SeedQueueProfiler.pop();
83 | }
84 |
85 | @Inject(
86 | method = "startWorkers",
87 | at = @At("TAIL")
88 | )
89 | private void profilePop(CallbackInfo ci) {
90 | SeedQueueProfiler.pop();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/sodium/profiling/ChunkRenderCacheLocalMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.sodium.profiling;
2 |
3 | import me.contaria.seedqueue.debug.SeedQueueProfiler;
4 | import me.jellysquid.mods.sodium.client.render.pipeline.context.ChunkRenderCacheLocal;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.injection.At;
7 | import org.spongepowered.asm.mixin.injection.Inject;
8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
9 |
10 | /**
11 | * Profiling mixins add more usage of the profiler to hot paths during wall rendering.
12 | * These Mixins will be removed in later versions of SeedQueue.
13 | */
14 | @Mixin(value = ChunkRenderCacheLocal.class, remap = false)
15 | public abstract class ChunkRenderCacheLocalMixin {
16 |
17 | @Inject(
18 | method = "",
19 | at = @At(
20 | value = "INVOKE",
21 | target = "Lme/jellysquid/mods/sodium/client/world/WorldSlice;(Lnet/minecraft/world/World;)V"
22 | ),
23 | remap = true
24 | )
25 | private void profileWorldSlice(CallbackInfo ci) {
26 | SeedQueueProfiler.push("world_slice");
27 | }
28 |
29 | @Inject(
30 | method = "",
31 | at = @At(
32 | value = "INVOKE",
33 | target = "Lme/jellysquid/mods/sodium/client/model/light/cache/ArrayLightDataCache;(Lnet/minecraft/world/BlockRenderView;)V"
34 | ),
35 | remap = true
36 | )
37 | private void profileLightDataCache(CallbackInfo ci) {
38 | SeedQueueProfiler.swap("light_data_cache");
39 | }
40 |
41 | @Inject(
42 | method = "",
43 | at = @At(
44 | value = "INVOKE",
45 | target = "Lme/jellysquid/mods/sodium/client/model/light/LightPipelineProvider;(Lme/jellysquid/mods/sodium/client/model/light/data/LightDataAccess;)V",
46 | remap = false
47 | ),
48 | remap = true
49 | )
50 | private void profileLightPipelineProvider(CallbackInfo ci) {
51 | SeedQueueProfiler.swap("light_pipeline_provider");
52 | }
53 |
54 | @Inject(
55 | method = "",
56 | at = @At(
57 | value = "INVOKE",
58 | target = "Lme/jellysquid/mods/sodium/client/render/pipeline/context/ChunkRenderCacheLocal;createBiomeColorBlender()Lme/jellysquid/mods/sodium/client/model/quad/blender/BiomeColorBlender;",
59 | remap = false
60 | ),
61 | remap = true
62 | )
63 | private void profileBiomeColorBlender(CallbackInfo ci) {
64 | SeedQueueProfiler.swap("biome_color_blender");
65 | }
66 |
67 | @Inject(
68 | method = "",
69 | at = @At(
70 | value = "INVOKE",
71 | target = "Lme/jellysquid/mods/sodium/client/render/pipeline/BlockRenderer;(Lnet/minecraft/client/MinecraftClient;Lme/jellysquid/mods/sodium/client/model/light/LightPipelineProvider;Lme/jellysquid/mods/sodium/client/model/quad/blender/BiomeColorBlender;)V"
72 | ),
73 | remap = true
74 | )
75 | private void profileBlockRenderer(CallbackInfo ci) {
76 | SeedQueueProfiler.swap("block_renderer");
77 | }
78 |
79 | @Inject(
80 | method = "",
81 | at = @At(
82 | value = "INVOKE",
83 | target = "Lme/jellysquid/mods/sodium/client/render/pipeline/FluidRenderer;(Lnet/minecraft/client/MinecraftClient;Lme/jellysquid/mods/sodium/client/model/light/LightPipelineProvider;Lme/jellysquid/mods/sodium/client/model/quad/blender/BiomeColorBlender;)V"
84 | ),
85 | remap = true
86 | )
87 | private void profileFluidRenderer(CallbackInfo ci) {
88 | SeedQueueProfiler.swap("fluid_renderer");
89 | }
90 |
91 | @Inject(
92 | method = "",
93 | at = @At(
94 | value = "INVOKE",
95 | target = "Lnet/minecraft/client/render/model/BakedModelManager;getBlockModels()Lnet/minecraft/client/render/block/BlockModels;"
96 | ),
97 | remap = true
98 | )
99 | private void profileBlockModels(CallbackInfo ci) {
100 | SeedQueueProfiler.swap("block_models");
101 | }
102 |
103 | @Inject(
104 | method = "",
105 | at = @At("TAIL"),
106 | remap = true
107 | )
108 | private void profilePop(CallbackInfo ci) {
109 | SeedQueueProfiler.pop();
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/sodium/profiling/MultidrawChunkRenderBackendMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.sodium.profiling;
2 |
3 | import me.contaria.seedqueue.debug.SeedQueueProfiler;
4 | import me.jellysquid.mods.sodium.client.render.chunk.backends.multidraw.MultidrawChunkRenderBackend;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.injection.At;
7 | import org.spongepowered.asm.mixin.injection.Inject;
8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
9 |
10 | /**
11 | * Profiling mixins add more usage of the profiler to hot paths during wall rendering.
12 | * These Mixins will be removed in later versions of SeedQueue.
13 | */
14 | @Mixin(value = MultidrawChunkRenderBackend.class, remap = false)
15 | public abstract class MultidrawChunkRenderBackendMixin {
16 |
17 | @Inject(
18 | method = "upload",
19 | at = @At(
20 | value = "INVOKE",
21 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/backends/multidraw/MultidrawChunkRenderBackend;setupUploadBatches(Ljava/util/Iterator;)V"
22 | )
23 | )
24 | private void profileSetupUploadBatches(CallbackInfo ci) {
25 | SeedQueueProfiler.push("setup_upload_batches");
26 | }
27 |
28 | @Inject(
29 | method = "upload",
30 | at = @At(
31 | value = "INVOKE",
32 | target = "Lme/jellysquid/mods/sodium/client/gl/device/CommandList;bindBuffer(Lme/jellysquid/mods/sodium/client/gl/buffer/GlBufferTarget;Lme/jellysquid/mods/sodium/client/gl/buffer/GlBuffer;)V"
33 | )
34 | )
35 | private void profileBindBuffer(CallbackInfo ci) {
36 | SeedQueueProfiler.swap("bind_buffer");
37 | }
38 |
39 | @Inject(
40 | method = "upload",
41 | at = @At(
42 | value = "INVOKE",
43 | target = "Lme/jellysquid/mods/sodium/client/gl/device/CommandList;bindBuffer(Lme/jellysquid/mods/sodium/client/gl/buffer/GlBufferTarget;Lme/jellysquid/mods/sodium/client/gl/buffer/GlBuffer;)V",
44 | shift = At.Shift.AFTER
45 | )
46 | )
47 | private void profileIterateQueue(CallbackInfo ci) {
48 | SeedQueueProfiler.swap("iterate_queue");
49 | }
50 |
51 | @Inject(
52 | method = "upload",
53 | at = @At(
54 | value = "INVOKE",
55 | target = "Lit/unimi/dsi/fastutil/objects/ObjectArrayFIFOQueue;dequeue()Ljava/lang/Object;"
56 | )
57 | )
58 | private void profileDequeueUpload(CallbackInfo ci) {
59 | SeedQueueProfiler.push("dequeue_upload");
60 | }
61 |
62 | @Inject(
63 | method = "upload",
64 | at = @At(
65 | value = "INVOKE",
66 | target = "Lme/jellysquid/mods/sodium/client/gl/arena/GlBufferArena;prepareBuffer(Lme/jellysquid/mods/sodium/client/gl/device/CommandList;I)V"
67 | )
68 | )
69 | private void profilePrepareBuffer(CallbackInfo ci) {
70 | SeedQueueProfiler.swap("prepare_buffer");
71 | }
72 |
73 | @Inject(
74 | method = "upload",
75 | at = @At(
76 | value = "INVOKE",
77 | target = "Lme/jellysquid/mods/sodium/client/gl/arena/GlBufferArena;prepareBuffer(Lme/jellysquid/mods/sodium/client/gl/device/CommandList;I)V",
78 | shift = At.Shift.AFTER
79 | )
80 | )
81 | private void profileIterateResults(CallbackInfo ci) {
82 | SeedQueueProfiler.swap("iterate_results");
83 | }
84 |
85 | @Inject(
86 | method = "upload",
87 | at = @At(
88 | value = "INVOKE",
89 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegion;getTessellation()Lme/jellysquid/mods/sodium/client/gl/tessellation/GlTessellation;",
90 | ordinal = 0
91 | )
92 | )
93 | private void profileUpdateTesselation(CallbackInfo ci) {
94 | SeedQueueProfiler.swap("update_tesselation");
95 | }
96 |
97 | @Inject(
98 | method = "upload",
99 | at = @At(
100 | value = "INVOKE",
101 | target = "Lit/unimi/dsi/fastutil/objects/ObjectArrayList;clear()V"
102 | )
103 | )
104 | private void profileClearQueue(CallbackInfo ci) {
105 | SeedQueueProfiler.swap("clear_queue");
106 | }
107 |
108 | @Inject(
109 | method = "upload",
110 | at = @At(
111 | value = "INVOKE",
112 | target = "Lit/unimi/dsi/fastutil/objects/ObjectArrayList;clear()V",
113 | shift = At.Shift.AFTER
114 | )
115 | )
116 | private void profilePop_iterateQueue(CallbackInfo ci) {
117 | SeedQueueProfiler.pop();
118 | }
119 |
120 | @Inject(
121 | method = "upload",
122 | at = @At(
123 | value = "INVOKE",
124 | target = "Lme/jellysquid/mods/sodium/client/gl/device/CommandList;invalidateBuffer(Lme/jellysquid/mods/sodium/client/gl/buffer/GlMutableBuffer;)V"
125 | )
126 | )
127 | private void profileInvalidateBuffer(CallbackInfo ci) {
128 | SeedQueueProfiler.swap("invalidate_buffer");
129 | }
130 |
131 | @Inject(
132 | method = "upload",
133 | at = @At("RETURN")
134 | )
135 | private void profilePop_upload(CallbackInfo ci) {
136 | SeedQueueProfiler.pop();
137 | }
138 |
139 | @Inject(
140 | method = "setupUploadBatches",
141 | at = @At(
142 | value = "INVOKE",
143 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegionManager;getRegion(III)Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegion;"
144 | )
145 | )
146 | private void profileGetRegion(CallbackInfo ci) {
147 | SeedQueueProfiler.push("get_region");
148 | }
149 |
150 | @Inject(
151 | method = "setupUploadBatches",
152 | at = @At(
153 | value = "INVOKE",
154 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/ChunkRenderContainer;setData(Lme/jellysquid/mods/sodium/client/render/chunk/data/ChunkRenderData;)V"
155 | )
156 | )
157 | private void profilePop_setupUploadBatches_1(CallbackInfo ci) {
158 | SeedQueueProfiler.pop();
159 | }
160 |
161 | @Inject(
162 | method = "setupUploadBatches",
163 | at = @At(
164 | value = "INVOKE",
165 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegionManager;getOrCreateRegion(III)Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegion;"
166 | )
167 | )
168 | private void profileCreateRegion(CallbackInfo ci) {
169 | SeedQueueProfiler.swap("create_region");
170 | }
171 |
172 | @Inject(
173 | method = "setupUploadBatches",
174 | at = @At(
175 | value = "INVOKE",
176 | target = "Lme/jellysquid/mods/sodium/client/render/chunk/region/ChunkRegion;getUploadQueue()Lit/unimi/dsi/fastutil/objects/ObjectArrayList;"
177 | )
178 | )
179 | private void profileAddToQueue(CallbackInfo ci) {
180 | SeedQueueProfiler.swap("add_to_queue");
181 | }
182 |
183 | @Inject(
184 | method = "setupUploadBatches",
185 | at = @At(
186 | value = "INVOKE",
187 | target = "Lit/unimi/dsi/fastutil/objects/ObjectArrayList;add(Ljava/lang/Object;)Z",
188 | shift = At.Shift.AFTER
189 | )
190 | )
191 | private void profilePop_setupUploadBatches_2(CallbackInfo ci) {
192 | SeedQueueProfiler.pop();
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/standardsettings/MinecraftClientMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.standardsettings;
2 |
3 | import com.bawnorton.mixinsquared.TargetHandler;
4 | import me.contaria.seedqueue.SeedQueue;
5 | import net.minecraft.client.MinecraftClient;
6 | import org.spongepowered.asm.mixin.Dynamic;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.injection.At;
9 | import org.spongepowered.asm.mixin.injection.Inject;
10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
11 |
12 | @Mixin(MinecraftClient.class)
13 | public abstract class MinecraftClientMixin {
14 |
15 | @Dynamic
16 | @TargetHandler(
17 | mixin = "me.contaria.standardsettings.mixin.MinecraftClientMixin",
18 | name = "reset"
19 | )
20 | @Inject(
21 | method = "@MixinSquared:Handler",
22 | at = @At("HEAD"),
23 | cancellable = true
24 | )
25 | private void loadSettingsCache(CallbackInfo ci) {
26 | if (!SeedQueue.inQueue() && SeedQueue.currentEntry != null && SeedQueue.currentEntry.loadSettingsCache()) {
27 | ci.cancel();
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/ThreadedAnvilChunkStorageMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.worldpreview;
2 |
3 | import com.bawnorton.mixinsquared.TargetHandler;
4 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
5 | import me.contaria.seedqueue.SeedQueue;
6 | import me.contaria.seedqueue.SeedQueueEntry;
7 | import me.contaria.seedqueue.interfaces.SQMinecraftServer;
8 | import me.voidxwalker.worldpreview.WorldPreviewProperties;
9 | import net.minecraft.client.MinecraftClient;
10 | import net.minecraft.client.options.Option;
11 | import net.minecraft.server.world.ServerWorld;
12 | import net.minecraft.server.world.ThreadedAnvilChunkStorage;
13 | import org.spongepowered.asm.mixin.*;
14 | import org.spongepowered.asm.mixin.injection.At;
15 |
16 | import java.util.Optional;
17 |
18 | @Mixin(value = ThreadedAnvilChunkStorage.class, priority = 1500)
19 | public abstract class ThreadedAnvilChunkStorageMixin {
20 |
21 | @Shadow
22 | @Final
23 | private ServerWorld world;
24 |
25 | @Dynamic
26 | @TargetHandler(
27 | mixin = "me.voidxwalker.worldpreview.mixin.server.ThreadedAnvilChunkStorageMixin",
28 | name = "worldpreview$sendData"
29 | )
30 | @ModifyExpressionValue(
31 | method = "@MixinSquared:Handler",
32 | at = @At(
33 | value = "FIELD",
34 | target = "Lme/voidxwalker/worldpreview/WorldPreview;properties:Lme/voidxwalker/worldpreview/WorldPreviewProperties;",
35 | remap = false
36 | )
37 | )
38 | private WorldPreviewProperties sendChunksToCorrectWorldPreview_inQueue(WorldPreviewProperties properties) {
39 | return this.getWorldPreviewProperties().orElse(this.isActiveServer() ? properties : null);
40 | }
41 |
42 | @Dynamic
43 | @TargetHandler(
44 | mixin = "me.voidxwalker.worldpreview.mixin.server.ThreadedAnvilChunkStorageMixin",
45 | name = "updateFrustum"
46 | )
47 | @ModifyExpressionValue(
48 | method = "@MixinSquared:Handler",
49 | at = @At(
50 | value = "FIELD",
51 | target = "Lnet/minecraft/client/options/GameOptions;fov:D"
52 | )
53 | )
54 | private double modifyCullingFov_inQueue(double fov) {
55 | if (!this.isActiveServer()) {
56 | // trying to keep track of the FOV in the settings cache / standardsettings is unnecessarily complicated
57 | // the majority of people will use quake pro with their personal FOV as fovOnWorldJoin anyway
58 | return Option.FOV.getMax();
59 | }
60 | return fov;
61 | }
62 |
63 | @Dynamic
64 | @TargetHandler(
65 | mixin = "me.voidxwalker.worldpreview.mixin.server.ThreadedAnvilChunkStorageMixin",
66 | name = "updateFrustum"
67 | )
68 | @ModifyExpressionValue(
69 | method = "@MixinSquared:Handler",
70 | at = @At(
71 | value = "INVOKE",
72 | target = "Lnet/minecraft/client/util/Window;getFramebufferWidth()I"
73 | )
74 | )
75 | private int modifyCullingWindowWidth(int width) {
76 | if (!this.isActiveServer()) {
77 | return SeedQueue.config.simulatedWindowSize.width();
78 | }
79 | return width;
80 | }
81 |
82 | @Dynamic
83 | @TargetHandler(
84 | mixin = "me.voidxwalker.worldpreview.mixin.server.ThreadedAnvilChunkStorageMixin",
85 | name = "updateFrustum"
86 | )
87 | @ModifyExpressionValue(
88 | method = "@MixinSquared:Handler",
89 | at = @At(
90 | value = "INVOKE",
91 | target = "Lnet/minecraft/client/util/Window;getFramebufferHeight()I"
92 | )
93 | )
94 | private int modifyCullingWindowHeight(int height) {
95 | if (!this.isActiveServer()) {
96 | return SeedQueue.config.simulatedWindowSize.height();
97 | }
98 | return height;
99 | }
100 |
101 | @Unique
102 | private Optional getWorldPreviewProperties() {
103 | return ((SQMinecraftServer) this.world.getServer()).seedQueue$getEntry().filter(entry -> !(SeedQueue.config.freezeLockedPreviews && entry.isLocked())).map(SeedQueueEntry::getPreviewProperties);
104 | }
105 |
106 | @Unique
107 | private boolean isActiveServer() {
108 | return this.world.getServer() == MinecraftClient.getInstance().getServer();
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/WorldPreviewMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.worldpreview;
2 |
3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
4 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
5 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
6 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
7 | import me.contaria.seedqueue.SeedQueueEntry;
8 | import me.contaria.seedqueue.compat.SeedQueuePreviewProperties;
9 | import me.contaria.seedqueue.interfaces.SQMinecraftServer;
10 | import me.contaria.speedrunapi.config.SpeedrunConfigAPI;
11 | import me.voidxwalker.worldpreview.WorldPreview;
12 | import net.minecraft.client.network.ClientPlayerEntity;
13 | import net.minecraft.client.network.ClientPlayerInteractionManager;
14 | import net.minecraft.client.render.Camera;
15 | import net.minecraft.client.world.ClientWorld;
16 | import net.minecraft.entity.data.DataTracker;
17 | import net.minecraft.entity.data.TrackedData;
18 | import net.minecraft.network.Packet;
19 | import net.minecraft.server.world.ServerWorld;
20 | import org.spongepowered.asm.mixin.Mixin;
21 | import org.spongepowered.asm.mixin.injection.At;
22 |
23 | import java.util.Optional;
24 | import java.util.Queue;
25 |
26 | @Mixin(WorldPreview.class)
27 | public abstract class WorldPreviewMixin {
28 |
29 | @WrapWithCondition(
30 | method = "configure",
31 | at = @At(
32 | value = "INVOKE",
33 | target = "Lnet/minecraft/entity/data/DataTracker;set(Lnet/minecraft/entity/data/TrackedData;Ljava/lang/Object;)V"
34 | )
35 | )
36 | private static boolean doNotSetPlayerModelParts_inQueue(DataTracker tracker, TrackedData> key, Object object, ServerWorld serverWorld) {
37 | return !((SQMinecraftServer) serverWorld.getServer()).seedQueue$inQueue();
38 | }
39 |
40 | @ModifyExpressionValue(
41 | method = "configure",
42 | at = @At(
43 | value = "FIELD",
44 | target = "Lnet/minecraft/client/options/GameOptions;perspective:I"
45 | )
46 | )
47 | private static int modifyPerspective_inQueue(int perspective, ServerWorld serverWorld) {
48 | if (((SQMinecraftServer) serverWorld.getServer()).seedQueue$inQueue()) {
49 | return (int) SpeedrunConfigAPI.getConfigValueOptionally("standardsettings", "perspective").orElse(0);
50 | }
51 | return perspective;
52 | }
53 |
54 | @WrapOperation(
55 | method = "configure",
56 | at = @At(
57 | value = "INVOKE",
58 | target = "Lme/voidxwalker/worldpreview/WorldPreview;set(Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/client/network/ClientPlayerEntity;Lnet/minecraft/client/network/ClientPlayerInteractionManager;Lnet/minecraft/client/render/Camera;Ljava/util/Queue;)V"
59 | )
60 | )
61 | private static void doNotConfigureWorldPreview_inQueue(ClientWorld world, ClientPlayerEntity player, ClientPlayerInteractionManager interactionManager, Camera camera, Queue> packetQueue, Operation original, ServerWorld serverWorld) {
62 | Optional entry = ((SQMinecraftServer) serverWorld.getServer()).seedQueue$getEntry();
63 | if (entry.isPresent()) {
64 | entry.get().setPreviewProperties(new SeedQueuePreviewProperties(world, player, interactionManager, camera, packetQueue));
65 | return;
66 | }
67 | original.call(world, player, interactionManager, camera, packetQueue);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/render/ClientWorldMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.worldpreview.render;
2 |
3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
4 | import me.contaria.seedqueue.SeedQueue;
5 | import me.voidxwalker.worldpreview.WorldPreview;
6 | import net.minecraft.client.render.WorldRenderer;
7 | import net.minecraft.client.world.ClientWorld;
8 | import org.objectweb.asm.Opcodes;
9 | import org.spongepowered.asm.mixin.Mixin;
10 | import org.spongepowered.asm.mixin.injection.At;
11 |
12 | @Mixin(ClientWorld.class)
13 | public abstract class ClientWorldMixin {
14 |
15 | // WorldPreview worlds always get created with the original WorldPreview#worldRenderer,
16 | // but on the Wall Screen a different WorldRenderer is used,
17 | // so we redirect to the currently used WorldRenderer instead
18 | @ModifyExpressionValue(
19 | method = "*",
20 | at = @At(
21 | value = "FIELD",
22 | target = "Lnet/minecraft/client/world/ClientWorld;worldRenderer:Lnet/minecraft/client/render/WorldRenderer;",
23 | opcode = Opcodes.GETFIELD
24 | )
25 | )
26 | private WorldRenderer modifyWorldRenderer(WorldRenderer worldRenderer) {
27 | if (SeedQueue.isOnWall()) {
28 | return WorldPreview.worldRenderer;
29 | }
30 | return worldRenderer;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/render/InGameHudMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.worldpreview.render;
2 |
3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
4 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
5 | import me.contaria.seedqueue.SeedQueue;
6 | import net.minecraft.client.gui.DrawableHelper;
7 | import net.minecraft.client.gui.hud.ChatHud;
8 | import net.minecraft.client.gui.hud.InGameHud;
9 | import net.minecraft.client.gui.hud.SubtitlesHud;
10 | import net.minecraft.client.util.math.MatrixStack;
11 | import net.minecraft.text.Text;
12 | import org.objectweb.asm.Opcodes;
13 | import org.spongepowered.asm.mixin.Mixin;
14 | import org.spongepowered.asm.mixin.injection.At;
15 | import org.spongepowered.asm.mixin.injection.ModifyVariable;
16 |
17 | @Mixin(InGameHud.class)
18 | public abstract class InGameHudMixin extends DrawableHelper {
19 |
20 | @WrapWithCondition(
21 | method = "render",
22 | at = @At(
23 | value = "INVOKE",
24 | target = "Lnet/minecraft/client/gui/hud/SubtitlesHud;render(Lnet/minecraft/client/util/math/MatrixStack;)V"
25 | )
26 | )
27 | private boolean doNotRenderSubtitlesOnWall(SubtitlesHud subtitlesHud, MatrixStack matrices) {
28 | return !SeedQueue.isOnWall();
29 | }
30 |
31 | @WrapWithCondition(
32 | method = "render",
33 | at = @At(
34 | value = "INVOKE",
35 | target = "Lnet/minecraft/client/gui/hud/ChatHud;render(Lnet/minecraft/client/util/math/MatrixStack;I)V"
36 | )
37 | )
38 | private boolean doNotRenderChatOnWall(ChatHud chatHud, MatrixStack matrices, int i) {
39 | return !SeedQueue.isOnWall();
40 | }
41 |
42 | @ModifyExpressionValue(
43 | method = "render",
44 | at = @At(
45 | value = "FIELD",
46 | target = "Lnet/minecraft/client/gui/hud/InGameHud;overlayMessage:Lnet/minecraft/text/Text;",
47 | opcode = Opcodes.GETFIELD,
48 | ordinal = 0
49 | )
50 | )
51 | private Text doNotRenderOverlayMessageOnWall(Text overlayMessage) {
52 | if (SeedQueue.isOnWall()) {
53 | return null;
54 | }
55 | return overlayMessage;
56 | }
57 |
58 | @ModifyExpressionValue(
59 | method = "render",
60 | at = @At(
61 | value = "FIELD",
62 | target = "Lnet/minecraft/client/gui/hud/InGameHud;title:Lnet/minecraft/text/Text;",
63 | opcode = Opcodes.GETFIELD,
64 | ordinal = 0
65 | )
66 | )
67 | private Text doNotRenderTitleMessageOnWall(Text title) {
68 | if (SeedQueue.isOnWall()) {
69 | return null;
70 | }
71 | return title;
72 | }
73 |
74 | @ModifyVariable(
75 | method = "renderStatusBars",
76 | at = @At("STORE")
77 | )
78 | private boolean doNotRenderBlinkingHeartsOnWall(boolean blinking) {
79 | return blinking && !SeedQueue.isOnWall();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/render/WindowMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.worldpreview.render;
2 |
3 | import com.llamalad7.mixinextras.injector.ModifyReturnValue;
4 | import me.contaria.seedqueue.SeedQueue;
5 | import me.contaria.seedqueue.gui.wall.SeedQueueWallScreen;
6 | import net.minecraft.client.MinecraftClient;
7 | import net.minecraft.client.util.Window;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.Unique;
10 | import org.spongepowered.asm.mixin.injection.At;
11 |
12 | @Mixin(Window.class)
13 | public abstract class WindowMixin {
14 |
15 | @ModifyReturnValue(
16 | method = {
17 | "getWidth",
18 | "getFramebufferWidth"
19 | },
20 | at = @At("RETURN")
21 | )
22 | private int modifyWidthOnWall(int width) {
23 | if (this.shouldModifyWindowSize()) {
24 | return SeedQueue.config.simulatedWindowSize.width();
25 | }
26 | return width;
27 | }
28 |
29 | @ModifyReturnValue(
30 | method = "getScaledWidth",
31 | at = @At("RETURN")
32 | )
33 | private int modifyScaledWidthOnWall(int width) {
34 | if (this.shouldModifyWindowSize()) {
35 | return SeedQueue.config.simulatedWindowSize.width() / this.modifiedScaleFactor();
36 | }
37 | return width;
38 | }
39 |
40 | @ModifyReturnValue(
41 | method = {
42 | "getHeight",
43 | "getFramebufferHeight"
44 | },
45 | at = @At("RETURN")
46 | )
47 | private int modifyHeightOnWall(int height) {
48 | if (this.shouldModifyWindowSize()) {
49 | return SeedQueue.config.simulatedWindowSize.height();
50 | }
51 | return height;
52 | }
53 |
54 | @ModifyReturnValue(
55 | method = "getScaledHeight",
56 | at = @At("RETURN")
57 | )
58 | private int modifyScaledHeightOnWall(int height) {
59 | if (this.shouldModifyWindowSize()) {
60 | return SeedQueue.config.simulatedWindowSize.height() / this.modifiedScaleFactor();
61 | }
62 | return height;
63 | }
64 |
65 | @ModifyReturnValue(
66 | method = "getScaleFactor",
67 | at = @At("RETURN")
68 | )
69 | private double modifyScaleFactorOnWall(double scaleFactor) {
70 | if (this.shouldModifyWindowSize()) {
71 | return this.modifiedScaleFactor();
72 | }
73 | return scaleFactor;
74 | }
75 |
76 | @Unique
77 | private boolean shouldModifyWindowSize() {
78 | return MinecraftClient.getInstance().isOnThread() && SeedQueue.isOnWall() && SeedQueueWallScreen.shouldModifyWindowSize();
79 | }
80 |
81 | @Unique
82 | private int modifiedScaleFactor() {
83 | return SeedQueue.config.calculateSimulatedScaleFactor(MinecraftClient.getInstance().options.guiScale, MinecraftClient.getInstance().options.forceUnicodeFont);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/compat/worldpreview/render/WorldRendererMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.compat.worldpreview.render;
2 |
3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
4 | import me.contaria.seedqueue.SeedQueue;
5 | import me.contaria.seedqueue.interfaces.worldpreview.SQWorldRenderer;
6 | import net.minecraft.client.MinecraftClient;
7 | import net.minecraft.client.render.Camera;
8 | import net.minecraft.client.render.Frustum;
9 | import net.minecraft.client.render.WorldRenderer;
10 | import net.minecraft.client.util.math.MatrixStack;
11 | import net.minecraft.client.world.ClientWorld;
12 | import net.minecraft.util.math.Matrix4f;
13 | import net.minecraft.util.math.Vec3d;
14 | import net.minecraft.util.profiler.Profiler;
15 | import org.spongepowered.asm.mixin.Final;
16 | import org.spongepowered.asm.mixin.Mixin;
17 | import org.spongepowered.asm.mixin.Shadow;
18 | import org.spongepowered.asm.mixin.injection.At;
19 |
20 | @Mixin(WorldRenderer.class)
21 | public abstract class WorldRendererMixin implements SQWorldRenderer {
22 |
23 | @Shadow
24 | @Final
25 | private MinecraftClient client;
26 | @Shadow
27 | private ClientWorld world;
28 | @Shadow
29 | private int frame;
30 |
31 | @Shadow
32 | protected abstract void setupTerrain(Camera camera, Frustum frustum, boolean hasForcedFrustum, int frame, boolean spectator);
33 |
34 | @Shadow
35 | protected abstract void updateChunks(long limitTime);
36 |
37 | @WrapWithCondition(
38 | method = "render",
39 | at = @At(
40 | value = "INVOKE",
41 | target = "Lcom/mojang/blaze3d/systems/RenderSystem;clear(IZ)V"
42 | )
43 | )
44 | private boolean doNotClearOnWallScreen(int mask, boolean getError, MatrixStack matrices) {
45 | return !SeedQueue.isOnWall();
46 | }
47 |
48 | @Override
49 | public void seedQueue$buildChunks(MatrixStack matrices, Camera camera, Matrix4f projectionMatrix) {
50 | Profiler profiler = this.client.getProfiler();
51 |
52 | profiler.push("light_updates");
53 | this.world.getChunkManager().getLightingProvider().doLightUpdates(Integer.MAX_VALUE, true, true);
54 |
55 | profiler.swap("culling");
56 | Vec3d pos = camera.getPos();
57 | Frustum frustum = new Frustum(matrices.peek().getModel(), projectionMatrix);
58 | frustum.setPosition(pos.getX(), pos.getY(), pos.getZ());
59 |
60 | profiler.swap("terrain_setup");
61 | this.setupTerrain(camera, frustum, false, this.frame++, this.client.player.isSpectator());
62 |
63 | profiler.swap("updatechunks");
64 | this.updateChunks(0);
65 |
66 | profiler.pop();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/server/IntegratedServerMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.server;
2 |
3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
4 | import net.minecraft.client.network.ClientPlayNetworkHandler;
5 | import net.minecraft.server.integrated.IntegratedServer;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.injection.At;
8 |
9 | @Mixin(IntegratedServer.class)
10 | public abstract class IntegratedServerMixin extends MinecraftServerMixin {
11 |
12 | public IntegratedServerMixin(String string) {
13 | super(string);
14 | }
15 |
16 | @ModifyExpressionValue(
17 | method = "tick",
18 | at = @At(
19 | value = "INVOKE",
20 | target = "Lnet/minecraft/client/MinecraftClient;getNetworkHandler()Lnet/minecraft/client/network/ClientPlayNetworkHandler;"
21 | )
22 | )
23 | private ClientPlayNetworkHandler doNotPauseBackgroundWorlds(ClientPlayNetworkHandler networkHandler) {
24 | if (this.seedQueue$inQueue()) {
25 | return null;
26 | }
27 | return networkHandler;
28 | }
29 |
30 | @ModifyExpressionValue(
31 | method = "tick",
32 | at = @At(
33 | value = "INVOKE",
34 | target = "Ljava/lang/Math;max(II)I"
35 | )
36 | )
37 | private int doNotChangeViewDistanceInQueue(int viewDistance) {
38 | if (this.seedQueue$inQueue()) {
39 | return this.getPlayerManager().getViewDistance();
40 | }
41 | return viewDistance;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/server/optimization/ThreadedAnvilChunkStorageMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.server.optimization;
2 |
3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
5 | import me.contaria.seedqueue.interfaces.SQMinecraftServer;
6 | import net.minecraft.nbt.CompoundTag;
7 | import net.minecraft.server.world.ServerWorld;
8 | import net.minecraft.server.world.ThreadedAnvilChunkStorage;
9 | import net.minecraft.util.math.ChunkPos;
10 | import org.spongepowered.asm.mixin.Final;
11 | import org.spongepowered.asm.mixin.Mixin;
12 | import org.spongepowered.asm.mixin.Shadow;
13 | import org.spongepowered.asm.mixin.injection.At;
14 |
15 | @Mixin(ThreadedAnvilChunkStorage.class)
16 | public abstract class ThreadedAnvilChunkStorageMixin {
17 |
18 | @Shadow
19 | @Final
20 | private ServerWorld world;
21 |
22 | // careful with this, chunkcacher injects shortly after
23 | @WrapOperation(
24 | method = "getUpdatedChunkTag",
25 | at = @At(
26 | value = "INVOKE",
27 | target = "Lnet/minecraft/server/world/ThreadedAnvilChunkStorage;getNbt(Lnet/minecraft/util/math/ChunkPos;)Lnet/minecraft/nbt/CompoundTag;"
28 | )
29 | )
30 | private CompoundTag skipGettingNbtInQueue(ThreadedAnvilChunkStorage storage, ChunkPos pos, Operation original) {
31 | // we can skip checking storage for chunk nbt while we're in queue since no chunks have been saved yet anyway
32 | if (((SQMinecraftServer) this.world.getServer()).seedQueue$inQueue()) {
33 | return null;
34 | }
35 | return original.call(storage, pos);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/server/parity/EntityMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.server.parity;
2 |
3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
4 | import me.contaria.seedqueue.interfaces.SQMinecraftServer;
5 | import net.minecraft.client.MinecraftClient;
6 | import net.minecraft.entity.Entity;
7 | import net.minecraft.entity.EntityType;
8 | import net.minecraft.server.MinecraftServer;
9 | import net.minecraft.world.World;
10 | import org.spongepowered.asm.mixin.Mixin;
11 | import org.spongepowered.asm.mixin.injection.At;
12 |
13 | @Mixin(value = Entity.class, priority = 500)
14 | public abstract class EntityMixin {
15 |
16 | @ModifyExpressionValue(
17 | method = "",
18 | at = @At(
19 | value = "INVOKE",
20 | target = "Ljava/util/concurrent/atomic/AtomicInteger;incrementAndGet()I"
21 | )
22 | )
23 | private int incrementEntityIdPerServer(int id, EntityType> type, World world) {
24 | // fallback for worldpreview entities
25 | // technically if the counter overflows and wraps back all the way around,
26 | // it could naturally reach -1 but that is imo within the realm of "whatever, shit happens"
27 | if (id == -1) {
28 | return id;
29 | }
30 | MinecraftServer server = world.getServer();
31 | if (server == null) {
32 | // for entities created clientside, use the entity id counter of the currently active server
33 | // this preserves the behaviour of clientside entities affecting the serverside id counter
34 | server = MinecraftClient.getInstance().getServer();
35 | }
36 | if (server == null) {
37 | // for entities created clientside while no server is active just use the id
38 | return id;
39 | }
40 | // by storing the max entity id counter per server, we ensure entity ID's will be in order
41 | // we initialize them in MinecraftServerMixin with the current max id to preserve parity when seedqueue isn't active
42 | // and also avoid entity ID's starting at 0 on every server when using seedqueue
43 | return ((SQMinecraftServer) server).seedQueue$incrementAndGetEntityID();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/server/synchronization/BiomeMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.server.synchronization;
2 |
3 | import net.fabricmc.loader.api.FabricLoader;
4 | import net.minecraft.block.BlockState;
5 | import net.minecraft.world.biome.Biome;
6 | import net.minecraft.world.chunk.Chunk;
7 | import net.minecraft.world.gen.surfacebuilder.ConfiguredSurfaceBuilder;
8 | import net.minecraft.world.gen.surfacebuilder.SurfaceBuilder;
9 | import org.spongepowered.asm.mixin.*;
10 | import org.spongepowered.asm.mixin.injection.At;
11 | import org.spongepowered.asm.mixin.injection.Inject;
12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
13 |
14 | import java.util.Random;
15 |
16 | @Mixin(Biome.class)
17 | public abstract class BiomeMixin {
18 |
19 | @Unique
20 | private static final String initSeed = FabricLoader.getInstance().getMappingResolver().mapMethodName("intermediary", "net.minecraft.class_3523", "method_15306", "(J)V");
21 |
22 | @Shadow
23 | @Final
24 | protected ConfiguredSurfaceBuilder> surfaceBuilder;
25 |
26 | @Unique
27 | private boolean synchronizedAccess;
28 |
29 | @Inject(
30 | method = "*",
31 | at = @At("TAIL")
32 | )
33 | private void shouldSynchronizeAccess(CallbackInfo ci) {
34 | Class> clas = this.surfaceBuilder.surfaceBuilder.getClass();
35 | while (clas != SurfaceBuilder.class) {
36 | try {
37 | clas.getDeclaredMethod(initSeed, long.class);
38 | this.synchronizedAccess = true;
39 | break;
40 | } catch (NoSuchMethodException e) {
41 | clas = clas.getSuperclass();
42 | }
43 | }
44 | }
45 |
46 | /**
47 | * @author contaria
48 | * @reason Synchronize calls if necessary, WrapMethod is not used because it causes a lot of Object arrays to be allocated in hot code.
49 | */
50 | @Overwrite
51 | public void buildSurface(Random random, Chunk chunk, int x, int z, int worldHeight, double noise, BlockState defaultBlock, BlockState defaultFluid, int seaLevel, long seed) {
52 | if (this.synchronizedAccess) {
53 | synchronized (this.surfaceBuilder.surfaceBuilder) {
54 | this.surfaceBuilder.initSeed(seed);
55 | this.surfaceBuilder.generate(random, chunk, (Biome) (Object) this, x, z, worldHeight, noise, defaultBlock, defaultFluid, seaLevel, seed);
56 | }
57 | } else {
58 | this.surfaceBuilder.initSeed(seed);
59 | this.surfaceBuilder.generate(random, chunk, (Biome) (Object) this, x, z, worldHeight, noise, defaultBlock, defaultFluid, seaLevel, seed);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/server/synchronization/DirectionTransformationMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.server.synchronization;
2 |
3 | import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
4 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
5 | import net.minecraft.util.math.Direction;
6 | import net.minecraft.util.math.DirectionTransformation;
7 | import org.spongepowered.asm.mixin.Mixin;
8 |
9 | @Mixin(DirectionTransformation.class)
10 | public abstract class DirectionTransformationMixin {
11 |
12 | @WrapMethod(
13 | method = "map"
14 | )
15 | private Direction synchronizeMapDirections(Direction direction, Operation original) {
16 | synchronized (this) {
17 | return original.call(direction);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/server/synchronization/ScheduledTickMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.server.synchronization;
2 |
3 | import net.minecraft.world.ScheduledTick;
4 | import org.objectweb.asm.Opcodes;
5 | import org.spongepowered.asm.mixin.*;
6 | import org.spongepowered.asm.mixin.injection.At;
7 | import org.spongepowered.asm.mixin.injection.Redirect;
8 |
9 | import java.util.concurrent.atomic.AtomicLong;
10 |
11 | @Mixin(ScheduledTick.class)
12 | public abstract class ScheduledTickMixin {
13 |
14 | @Unique
15 | private static final AtomicLong atomicIdCounter = new AtomicLong();
16 |
17 | @Mutable
18 | @Shadow
19 | @Final
20 | private long id;
21 |
22 | @Redirect(
23 | method = "(Lnet/minecraft/util/math/BlockPos;Ljava/lang/Object;JLnet/minecraft/world/TickPriority;)V",
24 | at = @At(
25 | value = "FIELD",
26 | target = "Lnet/minecraft/world/ScheduledTick;id:J",
27 | opcode = Opcodes.PUTFIELD
28 | )
29 | )
30 | private void atomicIdCounter(ScheduledTick> tick, long l) {
31 | this.id = atomicIdCounter.incrementAndGet();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/me/contaria/seedqueue/mixin/server/synchronization/WeightedBlockStateProviderMixin.java:
--------------------------------------------------------------------------------
1 | package me.contaria.seedqueue.mixin.server.synchronization;
2 |
3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
5 | import net.minecraft.util.collection.WeightedList;
6 | import net.minecraft.world.gen.stateprovider.WeightedBlockStateProvider;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.injection.At;
9 |
10 | import java.util.Random;
11 |
12 | @Mixin(WeightedBlockStateProvider.class)
13 | public abstract class WeightedBlockStateProviderMixin {
14 |
15 | @WrapOperation(
16 | method = "addState",
17 | at = @At(
18 | value = "INVOKE",
19 | target = "Lnet/minecraft/util/collection/WeightedList;add(Ljava/lang/Object;I)Lnet/minecraft/util/collection/WeightedList;"
20 | )
21 | )
22 | private synchronized WeightedList> synchronizeAddState(WeightedList> list, Object item, int weight, Operation> original) {
23 | return original.call(list, item, weight);
24 | }
25 |
26 | @WrapOperation(
27 | method = "getBlockState",
28 | at = @At(
29 | value = "INVOKE",
30 | target = "Lnet/minecraft/util/collection/WeightedList;pickRandom(Ljava/util/Random;)Ljava/lang/Object;"
31 | )
32 | )
33 | private synchronized Object synchronizeGetBlockState(WeightedList> list, Random random, Operation