├── .github └── workflows │ └── build.yml ├── .gitignore ├── Jenkinsfile ├── LICENSE ├── README.md ├── build.gradle ├── buildSrc ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── ishland │ └── vmp │ └── buildscript │ └── ParseGItHubActionChangelog.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java ├── com │ └── ishland │ │ └── vmp │ │ ├── VMPMod.java │ │ ├── common │ │ ├── chunk │ │ │ ├── iteration │ │ │ │ └── ITickableChunkSource.java │ │ │ ├── loading │ │ │ │ ├── IEntityPortalInterface.java │ │ │ │ ├── IPOIAsyncPreload.java │ │ │ │ ├── async_chunks_on_player_login │ │ │ │ │ └── AsyncChunkLoadUtil.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ ├── chunkwatching │ │ │ ├── AreaPlayerChunkWatchingManager.java │ │ │ └── PlayerClientVDTracking.java │ │ ├── config │ │ │ └── Config.java │ │ ├── entity │ │ │ └── item │ │ │ │ └── CachingWaterState.java │ │ ├── general │ │ │ ├── cache_ops │ │ │ │ └── biome │ │ │ │ │ └── PreloadingBiome.java │ │ │ ├── collections │ │ │ │ └── ITypeFilterableList.java │ │ │ └── spawn_density_cap │ │ │ │ └── SpawnDensityCapperDensityCapDelegate.java │ │ ├── logging │ │ │ └── AsyncAppenderBootstrap.java │ │ ├── maps │ │ │ └── AreaMap.java │ │ ├── networking │ │ │ └── eventloops │ │ │ │ └── VMPEventLoops.java │ │ ├── package-info.java │ │ ├── playerwatching │ │ │ ├── EntityTrackerEntryExtension.java │ │ │ ├── EntityTrackerExtension.java │ │ │ ├── NearbyEntityTracking.java │ │ │ ├── ServerPlayerEntityExtension.java │ │ │ ├── TACSExtension.java │ │ │ └── compat │ │ │ │ ├── EntityPositionTransformer.java │ │ │ │ └── ValkyrienSkies2ShipPositionTransformer.java │ │ └── util │ │ │ └── SimpleObjectPool.java │ │ └── mixins │ │ ├── VMPMixinPlugin.java │ │ ├── access │ │ ├── IAbstractChunkHolder.java │ │ ├── IChunkDeltaUpdateS2CPacket.java │ │ ├── IChunkHolder.java │ │ ├── IChunkTicket.java │ │ ├── IClientConnection.java │ │ ├── IEntityTrackerEntry.java │ │ ├── IPointOfInterestSet.java │ │ ├── IServerChunkManager.java │ │ ├── IServerCommandSource.java │ │ ├── IServerCommonNetworkHandler.java │ │ ├── ISpreadPlayersCommandPile.java │ │ ├── IThreadedAnvilChunkStorage.java │ │ ├── IThreadedAnvilChunkStorageEntityTracker.java │ │ ├── IThreadedAnvilChunkStorageTicketManager.java │ │ └── ITrackedPosition.java │ │ ├── carpet │ │ └── MixinEntityPlayerMPFake.java │ │ ├── chunk │ │ ├── loading │ │ │ ├── MixinPointOfInterestStorage.java │ │ │ ├── async_chunk_on_player_login │ │ │ │ └── MixinServerConfigurationNetworkHandler.java │ │ │ ├── commands │ │ │ │ ├── MixinCommandFunctionManager.java │ │ │ │ └── MixinSpreadPlayersCommand.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ └── ticking │ │ │ └── MixinServerWorld.java │ │ ├── core │ │ └── package-info.java │ │ ├── entity │ │ ├── move_zero_velocity │ │ │ └── MixinEntity.java │ │ └── package-info.java │ │ ├── entitytracker │ │ ├── MixinThreadedAnvilChunkStorage.java │ │ └── MixinThreadedAnvilChunkStorageEntityTracker.java │ │ ├── general │ │ ├── biome_access │ │ │ ├── MixinBiomeAccess.java │ │ │ └── fast_chunk_access │ │ │ │ └── MixinWorldView.java │ │ ├── cache_ops │ │ │ └── package-info.java │ │ ├── collections │ │ │ └── MixinTypeFilterableList.java │ │ ├── no_locking │ │ │ └── MixinPalettedContainer.java │ │ ├── package-info.java │ │ └── spawn_density_cap │ │ │ └── MixinSpawnDensityCapperDensityCap.java │ │ ├── networking │ │ ├── avoid_deadlocks │ │ │ └── MixinClientConnection.java │ │ ├── eventloops │ │ │ ├── MixinClientConnection.java │ │ │ └── MixinServerNetworkIo.java │ │ └── no_flush │ │ │ └── MixinClientConnection.java │ │ ├── playerwatching │ │ ├── MixinChunkFilter.java │ │ ├── MixinServerPlayerEntity.java │ │ ├── MixinTACSCancelSending.java │ │ ├── MixinTACSCancelSendingKrypton.java │ │ ├── MixinThreadedAnvilChunkStorage.java │ │ ├── optimize_nearby_entity_tracking_lookups │ │ │ ├── MixinEntityTrackerEntry.java │ │ │ ├── MixinServerPlayerEntity.java │ │ │ ├── MixinThreadedAnvilChunkStorage.java │ │ │ └── MixinThreadedAnvilChunkStorageEntityTracker.java │ │ └── optimize_nearby_player_lookups │ │ │ ├── MixinMobEntity.java │ │ │ ├── MixinServerWorld.java │ │ │ ├── MixinSpawnDensityCapper.java │ │ │ └── MixinThreadedAnvilChunkStorage.java │ │ ├── ticketsystem │ │ ├── package-info.java │ │ └── ticketpropagator │ │ │ └── MixinChunkTicketManager.java │ │ └── timesource │ │ └── MixinMinecraftClient.java └── io │ └── papermc │ └── paper │ └── util │ ├── IntegerUtil.java │ ├── MCUtil.java │ └── misc │ └── Delayed8WayDistancePropagator2D.java └── resources ├── assets └── vmp │ └── icon.png ├── fabric.mod.json ├── vmp.accesswidener └── vmp.mixins.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Automatically build the project and run any configured tests for every push 2 | # and submitted pull request. This can help catch issues that only occur on 3 | # certain platforms or Java versions, and provides a first line of defence 4 | # against bad commits. 5 | 6 | name: build 7 | on: [pull_request, push] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: checkout repository 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - uses: actions/cache@v4 19 | with: 20 | path: | 21 | ~/.gradle/caches 22 | ~/.gradle/wrapper 23 | ./.gradle/loom-cache 24 | key: ${{ runner.os }}-gradle0-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 25 | restore-keys: | 26 | ${{ runner.os }}-gradle0- 27 | - name: validate gradle wrapper 28 | uses: gradle/wrapper-validation-action@v1 29 | - name: setup jdk 21 30 | uses: actions/setup-java@v4 31 | with: 32 | distribution: 'zulu' 33 | java-version: 21 34 | java-package: jdk 35 | - name: build 36 | run: ./gradlew build 37 | - name: upload to modrinth and curseforge 38 | run: ./gradlew modrinth curseforge 39 | if: github.ref == 'refs/heads/ver/1.21.4' 40 | env: 41 | MODRINTH_TOKEN: ${{ secrets.MODRINTH_UPLOAD_TOKEN }} 42 | CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }} 43 | GITHUB_EVENT_RAW_PATH: ${{ github.event_path }} 44 | continue-on-error: true 45 | - name: capture build artifacts 46 | uses: actions/upload-artifact@v4 47 | with: 48 | name: Artifacts 49 | path: build/libs/ 50 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { label 'slave' } 3 | options { timestamps() } 4 | stages { 5 | stage('SCM-SKIP') { 6 | steps { 7 | scmSkip(skipPattern:'.*\\[ci skip\\].*') 8 | } 9 | } 10 | stage('Build') { 11 | tools { 12 | jdk "OpenJDK 21" 13 | } 14 | steps { 15 | withMaven( 16 | maven: '3', 17 | mavenLocalRepo: '.repository', 18 | publisherStrategy: 'EXPLICIT' 19 | ) { 20 | sh 'git fetch --tags' 21 | sh 'git reset --hard' 22 | sh './gradlew clean build' 23 | } 24 | } 25 | post { 26 | success { 27 | archiveArtifacts "**/build/libs/*-all.jar" 28 | } 29 | failure { 30 | cleanWs() 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-2025 ishland 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Very Many Players 2 | 3 | [![Github-CI](https://github.com/RelativityMC/VMP-fabric/workflows/build/badge.svg)](https://github.com/RelativityMC/VMP-fabric/actions/workflows/build.yml) 4 | [![Build Status](https://ci.codemc.io/job/RelativityMC/job/VMP-fabric/job/ver%252F1.19.2/badge/icon)](https://ci.codemc.io/job/RelativityMC/job/VMP-fabric/job/ver%252F1.19.2/) 5 | [![Discord](https://img.shields.io/discord/756715786747248641?logo=discord&logoColor=white)](https://discord.io/ishlandbukkit) 6 | 7 | A Fabric mod designed to improve server performance at high playercounts. 8 | 9 | **VMP is still in early development and things may break. Please report any issues to our issue tracker.** 10 | 11 | ## So what is VMP? 12 | Very Many Players, or VMP for short, is a Fabric mod designed to improve general server performance at high playercount 13 | **without sacrificing vanilla functionality or behavior**. 14 | For the best performance it is recommended to use VMP along with [Lithium](https://modrinth.com/mod/lithium). 15 | 16 | ## How VMP achieves its performance improvements? 17 | 18 | *This list may contain features currently unreleased and only found in development builds* 19 | 20 | **Server-side game logic performance improvements:** 21 | - Uses area maps to optimize nearby packet sending and player lookups 22 | - Uses cache to optimize entity trackers, fluid state lookups, ingredient matching and biome lookup 23 | - Optimizes natural spawning with caches and other tricks 24 | - Optimizes entity tracking with area maps 25 | - Optimizes entity iteration for collisions 26 | - Optimizes ticket propagator using MCUtil from the Paper project (patch licensed under MIT) 27 | - Makes initial chunk loading async on player login 28 | - Makes several commands run async **only when issued by a player** 29 | 30 | **Client-side game logic performance improvements:** 31 | - Makes time source to use built-in Java time source instead of GLFW via JNI calls 32 | 33 | **Networking performance & responsiveness improvements:** 34 | - Uses our own chunk sending mechanism (optionally with packet-level rate-limiting) 35 | - Adds packet-level per-player render distance 36 | - Makes vanilla tcp connections more responsive using packet priority from raknetify 37 | (works best when the server is connected **without reverse proxies such as Velocity and SSH port forwarding**) 38 | - Mitigates several kinds of bot attacks with split event loops and optimizations 39 | 40 | **Other improvements:** 41 | - Uses AsyncAppender to improve logging performance and keep logging IO off the main thread 42 | 43 | **... and more** 44 | 45 | ## Support 46 | [Issue tracker](https://github.com/RelativityMC/VMP-fabric/issues) 47 | [Discord server](https://discord.gg/Kdy8NM5HW4) 48 | 49 | ## Building and setting up 50 | _Requires Java 17 or later, both to build it and to use it._ 51 | Use Git to clone this repository, **do NOT download zip** 52 | Run the following commands in the project directory: 53 | 54 | ```shell 55 | ./gradlew clean build 56 | ``` 57 | 58 | ## License 59 | License information can be found [here](/LICENSE). 60 | 61 | 62 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '1.7-SNAPSHOT' 3 | id 'maven-publish' 4 | id 'com.github.johnrengelman.shadow' version '7.1.0' 5 | id 'com.modrinth.minotaur' version '2.+' 6 | id 'com.matthewprenger.cursegradle' version '1.4.0' 7 | } 8 | 9 | sourceCompatibility = JavaVersion.VERSION_17 10 | targetCompatibility = JavaVersion.VERSION_17 11 | 12 | archivesBaseName = "${project.archives_base_name}-mc${project.minecraft_version}" 13 | version = project.mod_version + "." + getVersionSuffix() 14 | group = project.maven_group 15 | 16 | afterEvaluate { 17 | logger.lifecycle("Version String: ${version}") 18 | logger.lifecycle(com.ishland.vmp.buildscript.ParseGItHubActionChangelog.getChangelog()) 19 | } 20 | 21 | repositories { 22 | // Add repositories to retrieve artifacts from in here. 23 | // You should only use this when depending on other mods because 24 | // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. 25 | // See https://docs.gradle.org/current/userguide/declaring_repositories.html 26 | // for more information about repositories. 27 | 28 | mavenCentral() 29 | maven { url 'https://jitpack.io' } 30 | } 31 | 32 | loom { 33 | accessWidenerPath = file("src/main/resources/vmp.accesswidener") 34 | } 35 | 36 | repositories.named("Mojang", MavenArtifactRepository) { 37 | artifactUrls ArtifactRepositoryContainer.MAVEN_CENTRAL_URL 38 | } 39 | 40 | configurations { 41 | api.extendsFrom includeApi 42 | shadowInclude 43 | } 44 | 45 | dependencies { 46 | // To change the versions see the gradle.properties file 47 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 48 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" 49 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 50 | 51 | include implementation("com.ibm.async:asyncutil:${async_util_version}") 52 | 53 | // Fabric API. This is technically optional, but you probably want it anyway. 54 | // modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 55 | 56 | // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. 57 | // You may need to force-disable transitiveness on them. 58 | } 59 | 60 | afterEvaluate { 61 | migrateMappings.configure { 62 | outputDir = project.file("src/main/java") 63 | } 64 | } 65 | 66 | processResources { 67 | inputs.property "version", project.version + "+" + project.minecraft_version 68 | 69 | filesMatching("fabric.mod.json") { 70 | expand "version": project.version + "+" + project.minecraft_version 71 | } 72 | } 73 | 74 | tasks.withType(JavaCompile).configureEach { 75 | // ensure that the encoding is set to UTF-8, no matter what the system default is 76 | // this fixes some edge cases with special characters not displaying correctly 77 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 78 | // If Javadoc is generated, this must be specified in that task too. 79 | it.options.encoding = "UTF-8" 80 | } 81 | 82 | java { 83 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 84 | // if it is present. 85 | // If you remove this line, sources will not be generated. 86 | withSourcesJar() 87 | } 88 | 89 | jar { 90 | from("LICENSE") { 91 | rename { "${it}_${project.archivesBaseName}"} 92 | } 93 | } 94 | 95 | shadowJar { 96 | archiveClassifier = "all-dev" 97 | configurations = [ project.configurations.shadowInclude ] 98 | minimize() 99 | from("LICENSE") { 100 | rename { "${it}_${project.archivesBaseName}"} 101 | } 102 | exclude "/raknetify*" 103 | relocate "com.ishland.raknetify", "com.ishland.vmp.deps.raknetify" 104 | } 105 | 106 | //noinspection UnnecessaryQualifiedReference 107 | task("remapShadowJar", type: net.fabricmc.loom.task.RemapJarTask, dependsOn: shadowJar) { 108 | input = shadowJar.archiveFile 109 | archiveFileName = shadowJar.archiveFileName.get().replaceAll("-dev\\.jar\$", ".jar") 110 | addNestedDependencies = true 111 | } 112 | 113 | assemble.dependsOn(remapShadowJar) 114 | 115 | // configure the maven publication 116 | publishing { 117 | publications { 118 | mavenJava(MavenPublication) { 119 | from components.java 120 | } 121 | } 122 | 123 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 124 | repositories { 125 | // Add repositories to publish to here. 126 | // Notice: This block does NOT have the same function as the block in the top level. 127 | // The repositories here will be used for publishing your artifact, not for 128 | // retrieving dependencies. 129 | } 130 | } 131 | 132 | modrinth { 133 | token = System.getenv("MODRINTH_TOKEN") // This is the default. Remember to have the MODRINTH_TOKEN environment variable set or else this will fail, or set it to whatever you want - just make sure it stays private! 134 | projectId = "vmp-fabric" // This can be the project ID or the slug. Either will work! 135 | versionNumber = project.version + "+" + project.minecraft_version // You don't need to set this manually. Will fail if Modrinth has this version already 136 | versionName = project.version + " devbuild for " + project.minecraft_version 137 | versionType = "alpha" // This is the default -- can also be `beta` or `alpha` 138 | uploadFile = remapShadowJar // With Loom, this MUST be set to `remapJar` instead of `jar`! 139 | gameVersions = [project.minecraft_version] // Must be an array, even with only one version 140 | loaders = ["fabric"] // Must also be an array - no need to specify this if you're using Loom or ForgeGradle 141 | changelog = com.ishland.vmp.buildscript.ParseGItHubActionChangelog.getChangelog() 142 | } 143 | 144 | if (System.getenv("CURSEFORGE_TOKEN")) { 145 | curseforge { 146 | apiKey = System.getenv("CURSEFORGE_TOKEN") 147 | project { 148 | id = '552542' 149 | changelogType = "markdown" 150 | changelog = com.ishland.vmp.buildscript.ParseGItHubActionChangelog.getChangelog() 151 | releaseType = 'alpha' 152 | 153 | addGameVersion project.minecraft_version 154 | addGameVersion "Fabric" 155 | addGameVersion "Java 17" 156 | 157 | mainArtifact(remapShadowJar) { 158 | displayName = project.version + " devbuild for " + project.minecraft_version 159 | } 160 | } 161 | options { 162 | forgeGradleIntegration = false 163 | } 164 | } 165 | } 166 | 167 | String getVersionSuffix() { 168 | def stdout = new ByteArrayOutputStream() 169 | exec { 170 | commandLine 'git', 'describe', '--tags', '--dirty', '--broken' 171 | standardOutput = stdout 172 | } 173 | stdout = stdout.toString().strip() 174 | def suffix = "" 175 | if (stdout.endsWith("-dirty")) { 176 | stdout = stdout.substring(0, stdout.length() - "-dirty".length()) 177 | suffix = "-dirty" 178 | } 179 | if (stdout.endsWith("-broken")) { 180 | stdout = stdout.substring(0, stdout.length() - "-broken".length()) 181 | suffix = "-broken" 182 | } 183 | if (stdout.indexOf('-') < 0) { 184 | return "0" + suffix; 185 | } 186 | def split = stdout.split('-') 187 | return split[split.length - 2] + suffix 188 | } 189 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | sourceCompatibility = JavaVersion.VERSION_17 6 | targetCompatibility = JavaVersion.VERSION_17 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | 13 | 14 | dependencies { 15 | // https://mvnrepository.com/artifact/com.google.code.gson/gson 16 | implementation 'com.google.code.gson:gson:2.10.1' 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/com/ishland/vmp/buildscript/ParseGItHubActionChangelog.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.buildscript; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonObject; 7 | 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.util.ArrayList; 11 | import java.util.stream.StreamSupport; 12 | 13 | public class ParseGItHubActionChangelog { 14 | 15 | public static String getChangelog() throws Throwable { 16 | final String path = System.getenv("GITHUB_EVENT_RAW_PATH"); 17 | if (path == null || path.isBlank()) return "No changelog was specified. "; 18 | final JsonObject jsonObject = new Gson().fromJson(Files.readString(Path.of(path)), JsonObject.class); 19 | 20 | StringBuilder builder = new StringBuilder(); 21 | builder.append("This version is uploaded automatically by GitHub Actions. \n\n") 22 | .append("Changelog: \n"); 23 | final JsonArray commits = jsonObject.getAsJsonArray("commits"); 24 | if (commits.isEmpty()) { 25 | builder.append("No changes detected. \n"); 26 | } else { 27 | for (JsonElement commit : commits) { 28 | JsonObject object = commit.getAsJsonObject(); 29 | builder.append("- "); 30 | builder.append('[').append(object.get("id").getAsString(), 0, 8).append(']') 31 | .append('(').append(object.get("url").getAsString()).append(')'); 32 | builder.append(' '); 33 | builder.append(object.get("message").getAsString().split("\n")[0]); 34 | builder.append(" - "); 35 | builder.append(object.get("author").getAsJsonObject().get("name").getAsString()); 36 | builder.append(" ").append('\n'); 37 | } 38 | } 39 | return builder.toString(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx2G 3 | 4 | # Fabric Properties 5 | # check these on https://fabricmc.net/versions.html 6 | minecraft_version=1.21.4 7 | yarn_mappings=1.21.4+build.1 8 | loader_version=0.16.9 9 | 10 | # Mod Properties 11 | mod_version = 0.2.0+beta.7 12 | maven_group = com.ishland.vmp 13 | archives_base_name = vmp-fabric 14 | 15 | # Dependencies 16 | #fabric_version=0.41.0+1.17 17 | async_util_version=0.1.0 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RelativityMC/VMP-fabric/e1c39a4f3ca41f198a6a9834d393e49f1d770de6/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.10-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" 241 | -------------------------------------------------------------------------------- /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 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | // maven { 8 | // name = 'Cotton' 9 | // url = 'https://server.bbkr.space/artifactory/libs-release/' 10 | // } 11 | gradlePluginPortal() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/VMPMod.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp; 2 | 3 | import com.ibm.asyncutil.util.Combinators; 4 | import com.ishland.vmp.common.config.Config; 5 | import com.ishland.vmp.common.playerwatching.NearbyEntityTracking; 6 | import net.fabricmc.api.ModInitializer; 7 | import org.spongepowered.asm.mixin.MixinEnvironment; 8 | 9 | import java.util.List; 10 | 11 | public class VMPMod implements ModInitializer { 12 | @Override 13 | public void onInitialize() { 14 | Combinators.allOf(List.of()).toCompletableFuture(); // check asyncutil existence 15 | 16 | if (Config.USE_OPTIMIZED_ENTITY_TRACKING) { 17 | NearbyEntityTracking.init(); 18 | } 19 | 20 | // MixinEnvironment.getCurrentEnvironment().audit(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/chunk/iteration/ITickableChunkSource.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.chunk.iteration; 2 | 3 | import net.minecraft.server.world.ChunkHolder; 4 | 5 | public interface ITickableChunkSource { 6 | 7 | Iterable vmp$tickableChunksIterator(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/chunk/loading/IEntityPortalInterface.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.chunk.loading; 2 | 3 | public interface IEntityPortalInterface { 4 | 5 | // default CompletionStage getTeleportTargetAtAsync(ServerWorld destination) { 6 | // throw new AbstractMethodError(); 7 | // } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/chunk/loading/IPOIAsyncPreload.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.chunk.loading; 2 | 3 | import net.minecraft.server.world.ServerWorld; 4 | import net.minecraft.util.math.BlockPos; 5 | 6 | import java.util.concurrent.CompletableFuture; 7 | 8 | public interface IPOIAsyncPreload { 9 | 10 | CompletableFuture preloadChunksAtAsync(ServerWorld world, BlockPos pos, int radius); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/chunk/loading/async_chunks_on_player_login/AsyncChunkLoadUtil.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.chunk.loading.async_chunks_on_player_login; 2 | 3 | import com.ibm.asyncutil.locks.AsyncSemaphore; 4 | import com.ibm.asyncutil.locks.FairAsyncSemaphore; 5 | import com.ishland.vmp.mixins.access.IServerChunkManager; 6 | import com.ishland.vmp.mixins.access.IThreadedAnvilChunkStorage; 7 | import com.mojang.datafixers.util.Either; 8 | import net.minecraft.server.world.ChunkHolder; 9 | import net.minecraft.server.world.ChunkLevelType; 10 | import net.minecraft.server.world.ChunkLevels; 11 | import net.minecraft.server.world.ChunkTicketManager; 12 | import net.minecraft.server.world.ChunkTicketType; 13 | import net.minecraft.server.world.OptionalChunk; 14 | import net.minecraft.server.world.ServerChunkManager; 15 | import net.minecraft.server.world.ServerWorld; 16 | import net.minecraft.util.Unit; 17 | import net.minecraft.util.math.ChunkPos; 18 | import net.minecraft.world.chunk.Chunk; 19 | import net.minecraft.world.chunk.ChunkStatus; 20 | 21 | import java.util.concurrent.CompletableFuture; 22 | import java.util.function.Function; 23 | 24 | public class AsyncChunkLoadUtil { 25 | 26 | private static final ChunkTicketType ASYNC_CHUNK_LOAD = ChunkTicketType.create("vmp_async_chunk_load", (unit, unit2) -> 0); 27 | 28 | public static final AsyncSemaphore SEMAPHORE = new FairAsyncSemaphore(12); 29 | 30 | public static CompletableFuture> scheduleChunkLoad(ServerWorld world, ChunkPos pos) { 31 | return scheduleChunkLoadWithRadius(world, pos, 3); 32 | } 33 | 34 | public static CompletableFuture> scheduleChunkLoadWithRadius(ServerWorld world, ChunkPos pos, int radius) { 35 | return scheduleChunkLoadWithLevel(world, pos, 33 - radius); 36 | } 37 | 38 | public static CompletableFuture> scheduleChunkLoadToStatus(ServerWorld world, ChunkPos pos, ChunkStatus status) { 39 | return scheduleChunkLoadWithLevel(world, pos, ChunkLevels.getLevelFromStatus(status)); 40 | } 41 | 42 | public static CompletableFuture> scheduleChunkLoadWithLevel(ServerWorld world, ChunkPos pos, int level) { 43 | final ServerChunkManager chunkManager = world.getChunkManager(); 44 | final ChunkTicketManager ticketManager = ((IServerChunkManager) chunkManager).getTicketManager(); 45 | 46 | final CompletableFuture> future = SEMAPHORE.acquire() 47 | .toCompletableFuture() 48 | .thenComposeAsync(unused -> { 49 | ticketManager.addTicketWithLevel(ASYNC_CHUNK_LOAD, pos, level, Unit.INSTANCE); 50 | ((IServerChunkManager) chunkManager).invokeUpdateChunks(); 51 | final ChunkHolder chunkHolder = ((IThreadedAnvilChunkStorage) chunkManager.chunkLoadingManager).invokeGetCurrentChunkHolder(pos.toLong()); 52 | if (chunkHolder == null) { 53 | throw new IllegalStateException("Chunk not there when requested"); 54 | } 55 | final ChunkLevelType levelType = ChunkLevels.getType(level); 56 | return switch (levelType) { 57 | case INACCESSIBLE -> chunkHolder.load(ChunkLevels.getStatus(level), world.getChunkManager().chunkLoadingManager); 58 | case FULL -> chunkHolder.getAccessibleFuture().thenApply(either -> (OptionalChunk) (Object) either); 59 | case BLOCK_TICKING -> chunkHolder.getTickingFuture().thenApply(either -> (OptionalChunk) (Object) either); 60 | case ENTITY_TICKING -> chunkHolder.getEntityTickingFuture().thenApply(either -> (OptionalChunk) (Object) either); 61 | }; 62 | }, world.getServer()); 63 | future.whenCompleteAsync((unused, throwable) -> { 64 | SEMAPHORE.release(); 65 | if (throwable != null) throwable.printStackTrace(); 66 | ticketManager.removeTicketWithLevel(ASYNC_CHUNK_LOAD, pos, level, Unit.INSTANCE); 67 | }, world.getServer()); 68 | return future; 69 | } 70 | 71 | private static final ThreadLocal isRespawnChunkLoadFinished = ThreadLocal.withInitial(() -> false); 72 | 73 | public static void setIsRespawnChunkLoadFinished(boolean value) { 74 | isRespawnChunkLoadFinished.set(value); 75 | } 76 | 77 | public static boolean isRespawnChunkLoadFinished() { 78 | return isRespawnChunkLoadFinished.get(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/chunk/loading/package-info.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.chunk.loading; -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/chunk/package-info.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.chunk; -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/chunkwatching/AreaPlayerChunkWatchingManager.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.chunkwatching; 2 | 3 | import com.ishland.vmp.common.maps.AreaMap; 4 | import io.papermc.paper.util.MCUtil; 5 | import it.unimi.dsi.fastutil.objects.Object2LongMap; 6 | import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; 7 | import it.unimi.dsi.fastutil.objects.ObjectIterator; 8 | import net.minecraft.entity.SpawnGroup; 9 | import net.minecraft.server.network.ChunkFilter; 10 | import net.minecraft.server.network.ServerPlayerEntity; 11 | import net.minecraft.server.world.ServerChunkLoadingManager; 12 | import net.minecraft.util.math.ChunkPos; 13 | import net.minecraft.util.math.MathHelper; 14 | 15 | import java.util.Arrays; 16 | import java.util.Set; 17 | 18 | public class AreaPlayerChunkWatchingManager { 19 | 20 | public static final int GENERAL_PLAYER_AREA_MAP_DISTANCE = (int) Math.ceil( 21 | Arrays.stream(SpawnGroup.values()) 22 | .mapToInt(SpawnGroup::getImmediateDespawnRange) 23 | .reduce(0, Math::max) / 16.0 24 | ); 25 | 26 | private final AreaMap playerAreaMap; 27 | private final AreaMap generalPlayerAreaMap = new AreaMap<>(); 28 | private final Object2LongOpenHashMap positions = new Object2LongOpenHashMap<>(); 29 | private Listener addListener = null; 30 | private Listener removeListener = null; 31 | 32 | private int watchDistance = 5; 33 | 34 | public AreaPlayerChunkWatchingManager() { 35 | this(null, null, null); 36 | } 37 | 38 | public AreaPlayerChunkWatchingManager(Listener addListener, Listener removeListener, ServerChunkLoadingManager tacs) { 39 | this.addListener = addListener; 40 | this.removeListener = removeListener; 41 | 42 | this.playerAreaMap = new AreaMap<>( 43 | (object, x, z) -> { 44 | if (this.addListener != null) { 45 | this.addListener.accept(object, x, z); 46 | } 47 | }, 48 | (object, x, z) -> { 49 | if (this.removeListener != null) { 50 | this.removeListener.accept(object, x, z); 51 | } 52 | }, 53 | true); 54 | } 55 | 56 | public void tick() { 57 | for (Object2LongMap.Entry entry : this.positions.object2LongEntrySet()) { 58 | final ServerPlayerEntity player = entry.getKey(); 59 | final PlayerClientVDTracking vdTracking = (PlayerClientVDTracking) player; 60 | if (vdTracking.isClientViewDistanceChanged()) { 61 | vdTracking.getClientViewDistance(); 62 | final long pos = entry.getLongValue(); 63 | player.setChunkFilter(ChunkFilter.cylindrical(new ChunkPos(pos), this.getViewDistance(player))); 64 | this.movePlayer(pos, player); 65 | } 66 | } 67 | 68 | } 69 | 70 | public void setWatchDistance(int watchDistance) { 71 | this.watchDistance = Math.max(2, watchDistance); 72 | final ObjectIterator> iterator = positions.object2LongEntrySet().fastIterator(); 73 | while (iterator.hasNext()) { 74 | final Object2LongMap.Entry entry = iterator.next(); 75 | // if (this.isWatchDisabled(entry.getKey())) continue; 76 | 77 | entry.getKey().setChunkFilter(ChunkFilter.cylindrical(new ChunkPos(entry.getLongValue()), this.getViewDistance(entry.getKey()))); 78 | 79 | this.playerAreaMap.update( 80 | entry.getKey(), 81 | MCUtil.getCoordinateX(entry.getLongValue()), 82 | MCUtil.getCoordinateZ(entry.getLongValue()), 83 | getViewDistance(entry.getKey())); 84 | 85 | this.generalPlayerAreaMap.update( 86 | entry.getKey(), 87 | MCUtil.getCoordinateX(entry.getLongValue()), 88 | MCUtil.getCoordinateZ(entry.getLongValue()), 89 | GENERAL_PLAYER_AREA_MAP_DISTANCE); 90 | } 91 | } 92 | 93 | public int getWatchDistance() { 94 | return watchDistance; 95 | } 96 | 97 | public Set getPlayersWatchingChunk(long l) { 98 | return this.playerAreaMap.getObjectsInRange(l); 99 | } 100 | 101 | public Object[] getPlayersWatchingChunkArray(long coordinateKey) { 102 | return this.playerAreaMap.getObjectsInRangeArray(coordinateKey); 103 | } 104 | 105 | public Object[] getPlayersInGeneralAreaMap(long coordinateKey) { 106 | return this.generalPlayerAreaMap.getObjectsInRangeArray(coordinateKey); 107 | } 108 | 109 | public void add(ServerPlayerEntity player, long pos) { 110 | // System.out.println(String.format("addPlayer %s to %s", player, new ChunkPos(pos))); 111 | final int x = ChunkPos.getPackedX(pos); 112 | final int z = ChunkPos.getPackedZ(pos); 113 | 114 | this.playerAreaMap.add(player, x, z, getViewDistance(player)); 115 | this.generalPlayerAreaMap.add(player, x, z, GENERAL_PLAYER_AREA_MAP_DISTANCE); 116 | 117 | this.positions.put(player, MCUtil.getCoordinateKey(x, z)); 118 | } 119 | 120 | public void remove(ServerPlayerEntity player) { 121 | // System.out.println(String.format("removePlayer %s", player)); 122 | this.playerAreaMap.remove(player); 123 | this.generalPlayerAreaMap.remove(player); 124 | 125 | this.positions.removeLong(player); 126 | } 127 | 128 | public void movePlayer(long currentPos, ServerPlayerEntity player) { 129 | // System.out.println(String.format("movePlayer %s to %s", player, new ChunkPos(currentPos))); 130 | // if (!this.isWatchDisabled(player)) 131 | final int x = ChunkPos.getPackedX(currentPos); 132 | final int z = ChunkPos.getPackedZ(currentPos); 133 | 134 | this.playerAreaMap.update(player, x, z, getViewDistance(player)); 135 | this.generalPlayerAreaMap.update(player, x, z, GENERAL_PLAYER_AREA_MAP_DISTANCE); 136 | 137 | this.positions.put(player, MCUtil.getCoordinateKey(x, z)); 138 | } 139 | 140 | private int getViewDistance(ServerPlayerEntity player) { 141 | return MathHelper.clamp(player.getViewDistance(), 2, this.watchDistance) + 1; // edge chunks are required for rendering 142 | } 143 | 144 | public interface Listener { 145 | void accept(ServerPlayerEntity player, int chunkX, int chunkZ); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/chunkwatching/PlayerClientVDTracking.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.chunkwatching; 2 | 3 | public interface PlayerClientVDTracking { 4 | 5 | boolean isClientViewDistanceChanged(); 6 | 7 | int getClientViewDistance(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/config/Config.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.config; 2 | 3 | import net.fabricmc.loader.api.FabricLoader; 4 | import net.fabricmc.loader.api.ModContainer; 5 | import net.fabricmc.loader.api.metadata.CustomValue; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.OutputStream; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.nio.file.StandardOpenOption; 15 | import java.util.Properties; 16 | 17 | public class Config { 18 | 19 | private static final Logger LOGGER = LogManager.getLogger(); 20 | 21 | public static final int TARGET_CHUNK_SEND_RATE; 22 | public static final boolean USE_ASYNC_LOGGING; 23 | public static final boolean USE_OPTIMIZED_ENTITY_TRACKING; 24 | public static final boolean OPTIMIZED_ENTITY_TRACKING_USE_STAGING_AREA; 25 | public static final boolean USE_MULTIPLE_NETTY_EVENT_LOOPS; 26 | public static final boolean USE_ASYNC_PORTALS; 27 | public static final boolean USE_ASYNC_CHUNKS_ON_LOGIN; 28 | public static final boolean USE_ASYNC_CHUNKS_ON_SOME_COMMANDS; 29 | public static final boolean SHOW_ASYNC_LOADING_MESSAGES; 30 | public static final boolean SHOW_CHUNK_TRACKING_MESSAGES; 31 | 32 | static { 33 | final Properties properties = new Properties(); 34 | final Properties newProperties = new Properties(); 35 | final Path path = FabricLoader.getInstance().getConfigDir().resolve("vmp.properties"); 36 | if (Files.isRegularFile(path)) { 37 | try (InputStream in = Files.newInputStream(path, StandardOpenOption.CREATE)) { 38 | properties.load(in); 39 | } catch (IOException e) { 40 | throw new RuntimeException(e); 41 | } 42 | } 43 | TARGET_CHUNK_SEND_RATE = getInt(properties, newProperties, "target_chunk_send_rate", -1); 44 | USE_ASYNC_LOGGING = getBoolean(properties, newProperties, "use_async_logging", true); 45 | USE_OPTIMIZED_ENTITY_TRACKING = getBoolean(properties, newProperties, "use_optimized_entity_tracking", true); 46 | OPTIMIZED_ENTITY_TRACKING_USE_STAGING_AREA = getBoolean(properties, newProperties, "optimized_entity_tracking_use_staging_area", true); 47 | USE_MULTIPLE_NETTY_EVENT_LOOPS = getBoolean(properties, newProperties, "use_multiple_netty_event_loops", true); 48 | USE_ASYNC_PORTALS = getBoolean(properties, newProperties, "use_async_portals", true); 49 | USE_ASYNC_CHUNKS_ON_LOGIN = getBoolean(properties, newProperties, "use_async_chunks_on_login", false); 50 | USE_ASYNC_CHUNKS_ON_SOME_COMMANDS = getBoolean(properties, newProperties, "use_async_chunks_on_some_commands", false); 51 | SHOW_ASYNC_LOADING_MESSAGES = getBoolean(properties, newProperties, "show_async_loading_messages", true); 52 | SHOW_CHUNK_TRACKING_MESSAGES = getBoolean(properties, newProperties, "show_chunk_tracking_messages", true); 53 | try (OutputStream out = Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { 54 | newProperties.store(out, "Configuration file for VMP"); 55 | } catch (IOException e) { 56 | throw new RuntimeException(e); 57 | } 58 | } 59 | 60 | public static void init() { 61 | } 62 | 63 | private static int getInt(Properties properties, Properties newProperties, String key, int def) { 64 | try { 65 | final int i = Integer.parseInt(properties.getProperty(key)); 66 | newProperties.setProperty(key, String.valueOf(i)); 67 | return i; 68 | } catch (NumberFormatException e) { 69 | newProperties.setProperty(key, String.valueOf(def)); 70 | return def; 71 | } 72 | } 73 | 74 | private static boolean getBoolean(Properties properties, Properties newProperties, String key, boolean def) { 75 | boolean boolean0 = getBoolean0(properties, newProperties, key, def); 76 | for (ModContainer modContainer : FabricLoader.getInstance().getAllMods()) { 77 | final CustomValue incompatibilitiesValue = modContainer.getMetadata().getCustomValue("vmp:incompatibleConfig"); 78 | if (incompatibilitiesValue != null && incompatibilitiesValue.getType() == CustomValue.CvType.ARRAY) { 79 | final CustomValue.CvArray incompatibilities = incompatibilitiesValue.getAsArray(); 80 | for (CustomValue value : incompatibilities) { 81 | if (value.getType() == CustomValue.CvType.STRING && value.getAsString().equals(key)) { 82 | final String message; 83 | if (Boolean.getBoolean("vmp.ignoreIncompatibleConfig")) { 84 | message = String.format("Ignoring incompatibility of %s (defined in %s@%s)", 85 | key, modContainer.getMetadata().getId(), modContainer.getMetadata().getVersion().getFriendlyString()); 86 | } else { 87 | message = String.format("Forcing %s in vmp.properties to be disabled (defined in %s@%s) (you may override this with -Dvmp.ignoreIncompatibleConfig=true)", 88 | key, modContainer.getMetadata().getId(), modContainer.getMetadata().getVersion().getFriendlyString()); 89 | boolean0 = false; 90 | } 91 | LOGGER.warn(message); 92 | } 93 | } 94 | } 95 | } 96 | return boolean0; 97 | } 98 | 99 | private static boolean getBoolean0(Properties properties, Properties newProperties, String key, boolean def) { 100 | try { 101 | final boolean b = parseBoolean(properties.getProperty(key)); 102 | newProperties.setProperty(key, String.valueOf(b)); 103 | return b; 104 | } catch (NumberFormatException e) { 105 | newProperties.setProperty(key, String.valueOf(def)); 106 | return def; 107 | } 108 | } 109 | 110 | private static boolean parseBoolean(String string) { 111 | if (string == null) throw new NumberFormatException("null"); 112 | if (string.trim().equalsIgnoreCase("true")) return true; 113 | if (string.trim().equalsIgnoreCase("false")) return false; 114 | throw new NumberFormatException(string); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/entity/item/CachingWaterState.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.entity.item; 2 | 3 | public interface CachingWaterState { 4 | 5 | boolean getLastWaterState(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/general/cache_ops/biome/PreloadingBiome.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.general.cache_ops.biome; 2 | 3 | import net.minecraft.registry.entry.RegistryEntry; 4 | import net.minecraft.world.ChunkRegion; 5 | import net.minecraft.world.biome.Biome; 6 | 7 | public interface PreloadingBiome { 8 | 9 | void vmp$tryPreloadBiome(ChunkRegion chunkRegion); 10 | 11 | void vmp$tryReloadBiome(ChunkRegion chunkRegion); 12 | 13 | RegistryEntry vmp$getBiomeCached(int x, int y, int z); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/general/collections/ITypeFilterableList.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.general.collections; 2 | 3 | public interface ITypeFilterableList { 4 | 5 | Object[] getBackingArray(); 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/general/spawn_density_cap/SpawnDensityCapperDensityCapDelegate.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.general.spawn_density_cap; 2 | 3 | import it.unimi.dsi.fastutil.objects.AbstractObject2IntMap; 4 | import it.unimi.dsi.fastutil.objects.AbstractObjectIterator; 5 | import it.unimi.dsi.fastutil.objects.AbstractObjectSet; 6 | import it.unimi.dsi.fastutil.objects.Object2IntMap; 7 | import it.unimi.dsi.fastutil.objects.ObjectIterator; 8 | import it.unimi.dsi.fastutil.objects.ObjectSet; 9 | import net.minecraft.entity.SpawnGroup; 10 | 11 | public class SpawnDensityCapperDensityCapDelegate { 12 | 13 | public static Object2IntMap delegateSpawnGroupDensities(int[] spawnGroupDensities) { 14 | return new AbstractObject2IntMap<>() { 15 | @Override 16 | public int size() { 17 | return spawnGroupDensities.length; 18 | } 19 | 20 | @Override 21 | public ObjectSet> object2IntEntrySet() { 22 | return new AbstractObjectSet<>() { 23 | @Override 24 | public ObjectIterator> iterator() { 25 | return new AbstractObjectIterator<>() { 26 | private int index = 0; 27 | 28 | @Override 29 | public boolean hasNext() { 30 | return index < spawnGroupDensities.length; 31 | } 32 | 33 | @Override 34 | public it.unimi.dsi.fastutil.objects.Object2IntMap.Entry next() { 35 | final int index = this.index; 36 | this.index++; 37 | return new AbstractObject2IntMap.BasicEntry<>( 38 | SpawnGroup.values()[index], spawnGroupDensities[index]); 39 | } 40 | }; 41 | } 42 | 43 | @Override 44 | public int size() { 45 | return spawnGroupDensities.length; 46 | } 47 | }; 48 | } 49 | 50 | @Override 51 | public int getInt(Object key) { 52 | return spawnGroupDensities[((SpawnGroup) key).ordinal()]; 53 | } 54 | }; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/logging/AsyncAppenderBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.logging; 2 | 3 | 4 | import com.ishland.vmp.common.config.Config; 5 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 6 | import it.unimi.dsi.fastutil.objects.ObjectIterator; 7 | import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; 8 | import org.apache.logging.log4j.Level; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | import org.apache.logging.log4j.core.Appender; 12 | import org.apache.logging.log4j.core.LoggerContext; 13 | import org.apache.logging.log4j.core.appender.AsyncAppender; 14 | import org.apache.logging.log4j.core.async.AsyncLoggerContext; 15 | import org.apache.logging.log4j.core.config.AbstractConfiguration; 16 | import org.apache.logging.log4j.core.config.AppenderRef; 17 | import org.apache.logging.log4j.core.config.LoggerConfig; 18 | import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; 19 | import org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder; 20 | import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; 21 | 22 | import java.util.Arrays; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | public class AsyncAppenderBootstrap { 27 | 28 | private static final Logger LOGGER = LogManager.getLogger(); 29 | 30 | private static final ObjectOpenHashSet appenderInCompatibilityMode = new ObjectOpenHashSet<>(new String[]{ 31 | "SysOut" 32 | }); 33 | 34 | public static void boot() { 35 | if (!Config.USE_ASYNC_LOGGING) return; 36 | try { 37 | if (LogManager.getContext(false) instanceof LoggerContext ctx && 38 | ctx.getConfiguration() instanceof AbstractConfiguration configuration) { 39 | 40 | if (ctx instanceof AsyncLoggerContext) { 41 | LOGGER.info("Logger is already async, skipping init async appender"); 42 | return; 43 | } 44 | 45 | final Object2ObjectOpenHashMap original = new Object2ObjectOpenHashMap<>(configuration.getAppenders()); 46 | final Object2ObjectOpenHashMap newMap = new Object2ObjectOpenHashMap<>(); 47 | 48 | final LoggerConfig config = ctx.getRootLogger().get(); 49 | for (AppenderRef appenderRef : config.getAppenderRefs()) { 50 | final AsyncAppender asyncAppender = new AsyncAppender.Builder<>() 51 | .setAppenderRefs(new AppenderRef[]{appenderRef}) 52 | .setName(appenderRef.getRef()) 53 | .setConfiguration(configuration) 54 | .build(); 55 | asyncAppender.start(); 56 | config.removeAppender(appenderRef.getRef()); 57 | config.addAppender(asyncAppender, null, null); 58 | newMap.put(appenderRef.getRef(), asyncAppender); 59 | } 60 | 61 | ctx.updateLoggers(); 62 | 63 | for (AppenderRef appenderRef : config.getAppenderRefs()) { 64 | for (LoggerConfig loggerConfig : configuration.getLoggers().values()) { 65 | if (loggerConfig.getAppenders().containsKey(appenderRef.getRef())) { 66 | loggerConfig.removeAppender(appenderRef.getRef()); 67 | loggerConfig.addAppender(newMap.get(appenderRef.getRef()), null, null); 68 | } 69 | } 70 | if (config.getAppenders().containsKey(appenderRef.getRef())) { 71 | config.getAppenders().remove(appenderRef.getRef()); 72 | config.addAppender(newMap.get(appenderRef.getRef()), null, null); 73 | } 74 | } 75 | 76 | LOGGER.info("Successfully started async appender with {}", original.keySet()); 77 | } else { 78 | LOGGER.error("Unsupported logger settings for async appender"); 79 | } 80 | } catch (Throwable t) { 81 | LOGGER.error("Error occurred while booting async appender", t); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/maps/AreaMap.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.maps; 2 | 3 | import com.ishland.vmp.common.util.SimpleObjectPool; 4 | import io.papermc.paper.util.MCUtil; 5 | import it.unimi.dsi.fastutil.longs.Long2ObjectFunction; 6 | import it.unimi.dsi.fastutil.longs.Long2ObjectMap; 7 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; 8 | import it.unimi.dsi.fastutil.longs.LongArrayList; 9 | import it.unimi.dsi.fastutil.longs.LongComparators; 10 | import it.unimi.dsi.fastutil.longs.LongIterator; 11 | import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; 12 | import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap; 13 | import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.util.Collection; 17 | import java.util.Comparator; 18 | import java.util.List; 19 | import java.util.ListIterator; 20 | import java.util.Set; 21 | 22 | public class AreaMap { 23 | 24 | private static final boolean DEBUG = Boolean.getBoolean("vmp.debugAreaMap"); 25 | 26 | private static final Object[] EMPTY = new Object[0]; 27 | private static final RawObjectLinkedOpenIdentityHashSet EMPTY_SET = new RawObjectLinkedOpenIdentityHashSet<>(); 28 | 29 | private final SimpleObjectPool> pooledHashSets = 30 | new SimpleObjectPool<>(unused -> new RawObjectLinkedOpenIdentityHashSet<>(), 31 | ReferenceLinkedOpenHashSet::clear, 32 | ts -> { 33 | ts.clear(); 34 | ts.trim(256); 35 | }, 36 | 8192 37 | ); 38 | private final Long2ObjectFunction> allocHashSet = unused -> pooledHashSets.alloc(); 39 | private final Long2ObjectOpenHashMap> map = new Long2ObjectOpenHashMap<>(); 40 | private final Reference2IntOpenHashMap viewDistances = new Reference2IntOpenHashMap<>(); 41 | private final Reference2LongOpenHashMap lastCenters = new Reference2LongOpenHashMap<>(); 42 | 43 | private Listener addListener = null; 44 | private Listener removeListener = null; 45 | private final boolean sortListenerCalls; 46 | 47 | public AreaMap() { 48 | this(null, null, false); 49 | } 50 | 51 | public AreaMap(Listener addListener, Listener removeListener, boolean sortListenerCalls) { 52 | this.addListener = addListener; 53 | this.removeListener = removeListener; 54 | this.sortListenerCalls = sortListenerCalls; 55 | } 56 | 57 | public Set getObjectsInRange(long coordinateKey) { 58 | final RawObjectLinkedOpenIdentityHashSet set = map.get(coordinateKey); 59 | return set != null ? set : (Set) EMPTY_SET; 60 | } 61 | 62 | public Object[] getObjectsInRangeArray(long coordinateKey) { 63 | final RawObjectLinkedOpenIdentityHashSet set = map.get(coordinateKey); 64 | return set != null ? set.getRawSet() : EMPTY; 65 | } 66 | 67 | public void add(T object, int x, int z, int rawViewDistance) { 68 | int viewDistance = rawViewDistance; 69 | viewDistances.put(object, viewDistance); 70 | lastCenters.put(object, MCUtil.getCoordinateKey(x, z)); 71 | 72 | final Listener addListener = this.addListener; 73 | if (this.sortListenerCalls && addListener != null) { 74 | final int length = 2 * viewDistance + 1; 75 | LongArrayList set = new LongArrayList(length * length); 76 | for (int xx = x - viewDistance; xx <= x + viewDistance; xx++) { 77 | for (int zz = z - viewDistance; zz <= z + viewDistance; zz++) { 78 | add0(xx, zz, object, false); 79 | set.add(MCUtil.getCoordinateKey(xx, zz)); 80 | } 81 | } 82 | notifyListenersSorted(object, x, z, addListener, set); 83 | } else { 84 | for (int xx = x - viewDistance; xx <= x + viewDistance; xx++) { 85 | for (int zz = z - viewDistance; zz <= z + viewDistance; zz++) { 86 | add0(xx, zz, object, true); 87 | } 88 | } 89 | } 90 | 91 | validate(object, x, z, viewDistance); 92 | } 93 | 94 | public void remove(T object) { 95 | if (!viewDistances.containsKey(object)) return; 96 | final int viewDistance = viewDistances.removeInt(object); 97 | final long lastCenter = lastCenters.removeLong(object); 98 | final int x = MCUtil.getCoordinateX(lastCenter); 99 | final int z = MCUtil.getCoordinateZ(lastCenter); 100 | 101 | final Listener removeListener = this.removeListener; 102 | if (this.sortListenerCalls && removeListener != null) { 103 | final int length = 2 * viewDistance + 1; 104 | LongArrayList set = new LongArrayList(length * length); 105 | for (int xx = x - viewDistance; xx <= x + viewDistance; xx++) { 106 | for (int zz = z - viewDistance; zz <= z + viewDistance; zz++) { 107 | remove0(xx, zz, object, false); 108 | set.add(MCUtil.getCoordinateKey(xx, zz)); 109 | } 110 | } 111 | notifyListenersSorted(object, x, z, removeListener, set); 112 | } else { 113 | for (int xx = x - viewDistance; xx <= x + viewDistance; xx++) { 114 | for (int zz = z - viewDistance; zz <= z + viewDistance; zz++) { 115 | remove0(xx, zz, object, true); 116 | } 117 | } 118 | } 119 | 120 | validate(object, -1, -1, -1); 121 | } 122 | 123 | public void update(T object, int x, int z, int rawViewDistance) { 124 | if (!viewDistances.containsKey(object)) 125 | throw new IllegalArgumentException("Tried to update %s when not in map".formatted(object)); 126 | final int viewDistance = rawViewDistance; 127 | final int oldViewDistance = viewDistances.replace(object, viewDistance); 128 | final long oldCenter = lastCenters.replace(object, MCUtil.getCoordinateKey(x, z)); 129 | final int oldX = MCUtil.getCoordinateX(oldCenter); 130 | final int oldZ = MCUtil.getCoordinateZ(oldCenter); 131 | 132 | updateAdds(object, oldX, oldZ, oldViewDistance, x, z, viewDistance); 133 | updateRemovals(object, oldX, oldZ, oldViewDistance, x, z, viewDistance); 134 | 135 | validate(object, x, z, viewDistance); 136 | } 137 | 138 | public int uniqueObjects() { 139 | return this.lastCenters.size(); 140 | } 141 | 142 | private void updateAdds(T object, int oldX, int oldZ, int oldViewDistance, int newX, int newZ, int newViewDistance) { 143 | int xLower = oldX - oldViewDistance; 144 | int xHigher = oldX + oldViewDistance; 145 | int zLower = oldZ - oldViewDistance; 146 | int zHigher = oldZ + oldViewDistance; 147 | 148 | final Listener addListener = this.addListener; 149 | if (this.sortListenerCalls && addListener != null) { 150 | final int length = 2 * newViewDistance + 1; 151 | LongArrayList set = new LongArrayList(length * length); 152 | for (int xx = newX - newViewDistance; xx <= newX + newViewDistance; xx++) { 153 | for (int zz = newZ - newViewDistance; zz <= newZ + newViewDistance; zz++) { 154 | if (!isInRange(xLower, xHigher, zLower, zHigher, xx, zz)) { 155 | add0(xx, zz, object, false); 156 | set.add(MCUtil.getCoordinateKey(xx, zz)); 157 | } 158 | } 159 | } 160 | notifyListenersSorted(object, newX, newZ, addListener, set); 161 | } else { 162 | for (int xx = newX - newViewDistance; xx <= newX + newViewDistance; xx++) { 163 | for (int zz = newZ - newViewDistance; zz <= newZ + newViewDistance; zz++) { 164 | if (!isInRange(xLower, xHigher, zLower, zHigher, xx, zz)) { 165 | add0(xx, zz, object, true); 166 | } 167 | } 168 | } 169 | } 170 | } 171 | 172 | private void updateRemovals(T object, int oldX, int oldZ, int oldViewDistance, int newX, int newZ, int newViewDistance) { 173 | int xLower = newX - newViewDistance; 174 | int xHigher = newX + newViewDistance; 175 | int zLower = newZ - newViewDistance; 176 | int zHigher = newZ + newViewDistance; 177 | 178 | final Listener removeListener = this.removeListener; 179 | if (this.sortListenerCalls && removeListener != null) { 180 | final int length = 2 * oldViewDistance + 1; 181 | LongArrayList set = new LongArrayList(length * length); 182 | for (int xx = oldX - oldViewDistance; xx <= oldX + oldViewDistance; xx++) { 183 | for (int zz = oldZ - oldViewDistance; zz <= oldZ + oldViewDistance; zz++) { 184 | if (!isInRange(xLower, xHigher, zLower, zHigher, xx, zz)) { 185 | remove0(xx, zz, object, false); 186 | set.add(MCUtil.getCoordinateKey(xx, zz)); 187 | } 188 | } 189 | } 190 | notifyListenersSorted(object, newX, newZ, removeListener, set); 191 | } else { 192 | for (int xx = oldX - oldViewDistance; xx <= oldX + oldViewDistance; xx++) { 193 | for (int zz = oldZ - oldViewDistance; zz <= oldZ + oldViewDistance; zz++) { 194 | if (!isInRange(xLower, xHigher, zLower, zHigher, xx, zz)) { 195 | remove0(xx, zz, object, true); 196 | } 197 | } 198 | } 199 | } 200 | } 201 | 202 | private void add0(int xx, int zz, T object, boolean notifyListeners) { 203 | final RawObjectLinkedOpenIdentityHashSet set = map.computeIfAbsent(MCUtil.getCoordinateKey(xx, zz), allocHashSet); 204 | set.add(object); 205 | if (notifyListeners && this.addListener != null) this.addListener.accept(object, xx, zz); 206 | } 207 | 208 | private void remove0(int xx, int zz, T object, boolean notifyListeners) { 209 | final long coordinateKey = MCUtil.getCoordinateKey(xx, zz); 210 | final RawObjectLinkedOpenIdentityHashSet set = map.get(coordinateKey); 211 | if (set == null) 212 | throw new IllegalStateException("Expect non-null set in [%d,%d]".formatted(xx, zz)); 213 | if (!set.remove(object)) 214 | throw new IllegalStateException("Expect %s in %s ([%d,%d])".formatted(object, set, xx, zz)); 215 | if (set.isEmpty()) { 216 | map.remove(coordinateKey); 217 | pooledHashSets.release(set); 218 | } 219 | if (notifyListeners && this.removeListener != null) this.removeListener.accept(object, xx, zz); 220 | } 221 | 222 | private void notifyListenersSorted(T object, int x, int z, Listener addListener, LongArrayList set) { 223 | set.sort(LongComparators.asLongComparator(Comparator.comparingLong(l -> chebyshevDistance(x, z, MCUtil.getCoordinateX(l), MCUtil.getCoordinateZ(l))))); 224 | final LongIterator iterator = set.iterator(); 225 | while (iterator.hasNext()) { 226 | final long pos = iterator.nextLong(); 227 | addListener.accept(object, MCUtil.getCoordinateX(pos), MCUtil.getCoordinateZ(pos)); 228 | } 229 | } 230 | 231 | private static boolean isInRange(int xLower, int xHigher, int zLower, int zHigher, int x, int z) { 232 | return x >= xLower && x <= xHigher && z >= zLower && z <= zHigher; 233 | } 234 | 235 | private static int chebyshevDistance(int x0, int z0, int x1, int z1) { 236 | return Math.max(Math.abs(x0 - x1), Math.abs(z0 - z1)); 237 | } 238 | 239 | // only for debugging 240 | private void validate(T object, int x, int z, int viewDistance) { 241 | if (!DEBUG) return; 242 | if (viewDistance < 0) { 243 | for (Long2ObjectMap.Entry> entry : map.long2ObjectEntrySet()) { 244 | if (entry.getValue().contains(object)) 245 | throw new IllegalStateException("Unexpected %s in %s ([%d,%d])".formatted(object, entry.getValue(), MCUtil.getCoordinateX(entry.getLongKey()), MCUtil.getCoordinateZ(entry.getLongKey()))); 246 | } 247 | } else { 248 | for (int xx = x - viewDistance; xx <= x + viewDistance; xx++) { 249 | for (int zz = z - viewDistance; zz <= z + viewDistance; zz++) { 250 | final long coordinateKey = MCUtil.getCoordinateKey(xx, zz); 251 | final RawObjectLinkedOpenIdentityHashSet set = map.get(coordinateKey); 252 | if (set == null) 253 | throw new IllegalStateException("Expect non-null set in [%d,%d]".formatted(xx, zz)); 254 | if (!set.contains(object)) 255 | throw new IllegalStateException("Expect %s in %s ([%d,%d])".formatted(object, set, xx, zz)); 256 | } 257 | } 258 | } 259 | } 260 | 261 | private static class RawObjectLinkedOpenIdentityHashSet extends ReferenceLinkedOpenHashSet { 262 | 263 | public RawObjectLinkedOpenIdentityHashSet() { 264 | } 265 | 266 | public Object[] getRawSet() { 267 | return this.key; 268 | } 269 | 270 | @Override 271 | protected void rehash(int newN) { 272 | if (newN > n) { // don't shrink automatically 273 | super.rehash(newN); 274 | } 275 | } 276 | } 277 | 278 | public interface Listener { 279 | void accept(T object, int x, int z); 280 | } 281 | 282 | } 283 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/networking/eventloops/VMPEventLoops.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.networking.eventloops; 2 | 3 | import com.google.common.base.Suppliers; 4 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.epoll.EpollEventLoopGroup; 8 | import io.netty.channel.epoll.EpollSocketChannel; 9 | import io.netty.channel.nio.NioEventLoop; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.nio.NioSocketChannel; 12 | import io.netty.util.concurrent.FastThreadLocalThread; 13 | import net.minecraft.network.NetworkPhase; 14 | 15 | import java.util.function.Supplier; 16 | 17 | public class VMPEventLoops { 18 | 19 | public static final Supplier NIO_LOGIN_EVENT_LOOP_GROUP = 20 | Suppliers.memoize(() -> new NioEventLoopGroup( 21 | 2, 22 | new ThreadFactoryBuilder() 23 | .setThreadFactory(FastThreadLocalThread::new) 24 | .setNameFormat("Netty Server Login IO #%d") 25 | .setDaemon(true) 26 | .build() 27 | ) 28 | ); 29 | 30 | public static final Supplier NIO_PLAY_EVENT_LOOP_GROUP = 31 | Suppliers.memoize(() -> new NioEventLoopGroup( 32 | 0, 33 | new ThreadFactoryBuilder() 34 | .setThreadFactory(FastThreadLocalThread::new) 35 | .setNameFormat("Netty Server Play IO #%d") 36 | .setDaemon(true) 37 | .build() 38 | ) 39 | ); 40 | 41 | public static final Supplier EPOLL_LOGIN_EVENT_LOOP_GROUP = 42 | Suppliers.memoize(() -> new EpollEventLoopGroup( 43 | 2, 44 | new ThreadFactoryBuilder() 45 | .setThreadFactory(FastThreadLocalThread::new) 46 | .setNameFormat("Netty Epoll Server Login IO #%d") 47 | .setDaemon(true) 48 | .build() 49 | ) 50 | ); 51 | 52 | public static final Supplier EPOLL_PLAY_EVENT_LOOP_GROUP = 53 | Suppliers.memoize(() -> new EpollEventLoopGroup( 54 | 0, 55 | new ThreadFactoryBuilder() 56 | .setThreadFactory(FastThreadLocalThread::new) 57 | .setNameFormat("Netty Epoll Server Play IO #%d") 58 | .setDaemon(true) 59 | .build() 60 | ) 61 | ); 62 | 63 | public static EventLoopGroup getEventLoopGroup(Channel channel, NetworkPhase state) { 64 | if (channel instanceof NioSocketChannel) { 65 | if (state == NetworkPhase.LOGIN) { 66 | return NIO_LOGIN_EVENT_LOOP_GROUP.get(); 67 | } else if (state == NetworkPhase.PLAY) { 68 | return NIO_PLAY_EVENT_LOOP_GROUP.get(); 69 | } 70 | } else if (channel instanceof EpollSocketChannel) { 71 | if (state == NetworkPhase.LOGIN) { 72 | return EPOLL_LOGIN_EVENT_LOOP_GROUP.get(); 73 | } else if (state == NetworkPhase.PLAY) { 74 | return EPOLL_PLAY_EVENT_LOOP_GROUP.get(); 75 | } 76 | } 77 | return null; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/package-info.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common; -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/playerwatching/EntityTrackerEntryExtension.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.playerwatching; 2 | 3 | public interface EntityTrackerEntryExtension { 4 | 5 | public void vmp$tickAlways(); 6 | 7 | public void vmp$syncEntityData(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/playerwatching/EntityTrackerExtension.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.playerwatching; 2 | 3 | import net.minecraft.server.network.ServerPlayerEntity; 4 | import net.minecraft.util.math.Vec3d; 5 | 6 | import java.util.Set; 7 | 8 | public interface EntityTrackerExtension { 9 | 10 | boolean isPositionUpdated(); 11 | 12 | void updatePosition(); 13 | 14 | Vec3d getPreviousLocation(); 15 | 16 | long getPreviousChunkPos(); 17 | 18 | void updateListeners(Set triedPlayers); 19 | 20 | void tryTick(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/playerwatching/ServerPlayerEntityExtension.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.playerwatching; 2 | 3 | public interface ServerPlayerEntityExtension { 4 | 5 | boolean vmpTracking$isPositionUpdated(); 6 | 7 | void vmpTracking$updatePosition(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/playerwatching/TACSExtension.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.playerwatching; 2 | 3 | import com.ishland.vmp.common.chunkwatching.AreaPlayerChunkWatchingManager; 4 | 5 | public interface TACSExtension { 6 | 7 | AreaPlayerChunkWatchingManager getAreaPlayerChunkWatchingManager(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/playerwatching/compat/EntityPositionTransformer.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.playerwatching.compat; 2 | 3 | import net.minecraft.entity.Entity; 4 | import net.minecraft.util.math.ChunkPos; 5 | import net.minecraft.util.math.Vec3d; 6 | 7 | public abstract class EntityPositionTransformer { 8 | 9 | protected abstract Vec3d transform0(Entity entity, Vec3d pos); 10 | 11 | public final Vec3d transform(Entity entity, Vec3d pos) { 12 | final Vec3d pos1; 13 | try { 14 | pos1 = transform0(entity, pos); 15 | } catch (Throwable t) { 16 | System.err.println("EntityPositionTransformer %s threw an exception for %s at %s".formatted(getClass().getName(), entity, pos)); 17 | t.printStackTrace(); 18 | return pos; 19 | } 20 | if (pos1 == null) { 21 | System.err.println("EntityPositionTransformer %s returned null for %s at %s".formatted(getClass().getName(), entity, pos)); 22 | return pos; 23 | } 24 | return pos1; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/playerwatching/compat/ValkyrienSkies2ShipPositionTransformer.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.playerwatching.compat; 2 | 3 | import net.minecraft.entity.Entity; 4 | import net.minecraft.util.math.ChunkPos; 5 | import net.minecraft.util.math.ChunkSectionPos; 6 | import net.minecraft.util.math.Vec3d; 7 | import net.minecraft.world.World; 8 | import org.joml.Matrix4dc; 9 | import org.joml.Vector3d; 10 | 11 | import java.lang.invoke.MethodHandle; 12 | import java.lang.invoke.MethodHandles; 13 | import java.lang.invoke.MethodType; 14 | 15 | public class ValkyrienSkies2ShipPositionTransformer extends EntityPositionTransformer { 16 | 17 | private static final MethodHandle methodVSGameUtilsKt$getShipManagingPos; 18 | private static final MethodHandle methodShip$getShipToWorld; 19 | 20 | static { 21 | try { 22 | final Class clazzVSGameUtilsKt = Class.forName("org.valkyrienskies.mod.common.VSGameUtilsKt"); 23 | final Class clazzShip = Class.forName("org.valkyrienskies.core.api.ships.Ship"); 24 | methodVSGameUtilsKt$getShipManagingPos = MethodHandles.lookup().findStatic(clazzVSGameUtilsKt, 25 | "getShipManagingPos", MethodType.methodType(clazzShip, World.class, int.class, int.class)); 26 | methodShip$getShipToWorld = MethodHandles.lookup().findVirtual(clazzShip, 27 | "getShipToWorld", MethodType.methodType(Matrix4dc.class)); 28 | } catch (Throwable t) { 29 | throw new RuntimeException(t); 30 | } 31 | } 32 | 33 | @Override 34 | public Vec3d transform0(Entity entity, Vec3d pos) { 35 | try { 36 | final Object ship = methodVSGameUtilsKt$getShipManagingPos.invoke(entity.getWorld(), ChunkSectionPos.getSectionCoord(pos.x), ChunkSectionPos.getSectionCoord(pos.z)); 37 | if (ship != null) { 38 | final Matrix4dc shipToWorld = (Matrix4dc) methodShip$getShipToWorld.invoke(ship); 39 | final Vector3d transformedPosition = shipToWorld.transformPosition(new Vector3d(pos.x, pos.y, pos.z)); 40 | return new Vec3d(transformedPosition.x, transformedPosition.y, transformedPosition.z); 41 | } 42 | } catch (Throwable t) { 43 | throw new RuntimeException(t); 44 | } 45 | return pos; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/common/util/SimpleObjectPool.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.common.util; 2 | 3 | import com.google.common.base.Preconditions; 4 | 5 | import java.util.Objects; 6 | import java.util.function.Consumer; 7 | import java.util.function.Function; 8 | 9 | public class SimpleObjectPool { 10 | 11 | private final Function, T> constructor; 12 | private final Consumer initializer; 13 | private final Consumer onRecycle; 14 | private final int size; 15 | 16 | private Object[] cachedObjects = null; 17 | private int allocatedCount = 0; 18 | 19 | public SimpleObjectPool(Function, T> constructor, Consumer initializer, Consumer onRecycle, int size) { 20 | this.constructor = Objects.requireNonNull(constructor); 21 | this.initializer = Objects.requireNonNull(initializer); 22 | this.onRecycle = Objects.requireNonNull(onRecycle); 23 | Preconditions.checkArgument(size > 0); 24 | this.size = size; 25 | } 26 | 27 | private void init() { 28 | if (this.cachedObjects == null) { 29 | this.cachedObjects = new Object[this.size]; 30 | 31 | for (int i = 0; i < size; i++) { 32 | final T object = this.constructor.apply(this); 33 | this.cachedObjects[i] = object; 34 | } 35 | } 36 | } 37 | 38 | public T alloc() { 39 | final T object; 40 | synchronized (this) { 41 | init(); 42 | if (this.allocatedCount >= this.size) { // oversized, falling back to normal alloc 43 | object = this.constructor.apply(this); 44 | return object; 45 | } 46 | 47 | // get an object from the array 48 | final int ordinal = this.allocatedCount++; 49 | object = (T) this.cachedObjects[ordinal]; 50 | this.cachedObjects[ordinal] = null; 51 | } 52 | 53 | this.initializer.accept(object); // initialize the object 54 | 55 | return object; 56 | } 57 | 58 | public void release(T object) { 59 | synchronized (this) { 60 | init(); 61 | if (this.allocatedCount == 0) return; // pool is full 62 | 63 | this.onRecycle.accept(object); 64 | this.cachedObjects[--this.allocatedCount] = object; // store the object into the pool 65 | } 66 | } 67 | 68 | public int getAllocatedCount() { 69 | return this.allocatedCount; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/VMPMixinPlugin.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins; 2 | 3 | import com.ishland.vmp.common.config.Config; 4 | import com.ishland.vmp.common.logging.AsyncAppenderBootstrap; 5 | import net.fabricmc.loader.api.FabricLoader; 6 | import org.objectweb.asm.tree.ClassNode; 7 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; 8 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 9 | 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | public class VMPMixinPlugin implements IMixinConfigPlugin { 14 | @Override 15 | public void onLoad(String mixinPackage) { 16 | Config.init(); 17 | AsyncAppenderBootstrap.boot(); 18 | } 19 | 20 | @Override 21 | public String getRefMapperConfig() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { 27 | if (mixinClassName.startsWith("com.ishland.vmp.mixins.carpet.")) 28 | return FabricLoader.getInstance().isModLoaded("carpet"); 29 | 30 | if (mixinClassName.startsWith("com.ishland.vmp.mixins.playerwatching.optimize_nearby_entity_tracking_lookups")) 31 | return Config.USE_OPTIMIZED_ENTITY_TRACKING; 32 | if (mixinClassName.startsWith("com.ishland.vmp.mixins.networking.eventloops.")) 33 | return Config.USE_MULTIPLE_NETTY_EVENT_LOOPS; 34 | if (mixinClassName.startsWith("com.ishland.vmp.mixins.chunk.loading.portals.")) 35 | return Config.USE_ASYNC_PORTALS; 36 | if (mixinClassName.startsWith("com.ishland.vmp.mixins.chunk.loading.async_chunk_on_player_login")) 37 | return Config.USE_ASYNC_CHUNKS_ON_LOGIN && !isClassExist("com.ishland.c2me.opts.chunkio.common.async_chunk_on_player_login.IAsyncChunkPlayer"); 38 | if (mixinClassName.startsWith("com.ishland.vmp.mixins.chunk.loading.command")) 39 | return Config.USE_ASYNC_CHUNKS_ON_SOME_COMMANDS; 40 | if (mixinClassName.equals("com.ishland.vmp.mixins.playerwatching.MixinTACSCancelSendingKrypton")) 41 | return FabricLoader.getInstance().isModLoaded("krypton"); 42 | if (mixinClassName.startsWith("com.ishland.vmp.mixins.networking.avoid_deadlocks")) 43 | return !FabricLoader.getInstance().isModLoaded("raknetify"); 44 | return true; 45 | } 46 | 47 | @Override 48 | public void acceptTargets(Set myTargets, Set otherTargets) { 49 | 50 | } 51 | 52 | @Override 53 | public List getMixins() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 59 | 60 | } 61 | 62 | @Override 63 | public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 64 | 65 | } 66 | 67 | private static boolean isClassExist(String name) { 68 | try { 69 | Class.forName(name); 70 | return true; 71 | } catch (ClassNotFoundException e) { 72 | return false; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IAbstractChunkHolder.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.server.world.ServerChunkLoadingManager; 4 | import net.minecraft.world.chunk.AbstractChunkHolder; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Invoker; 7 | 8 | @Mixin(AbstractChunkHolder.class) 9 | public interface IAbstractChunkHolder { 10 | 11 | @Invoker 12 | void invokeUpdateStatus(ServerChunkLoadingManager chunkStorage); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IChunkDeltaUpdateS2CPacket.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.network.packet.s2c.play.ChunkDeltaUpdateS2CPacket; 5 | import net.minecraft.util.math.ChunkSectionPos; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Mutable; 8 | import org.spongepowered.asm.mixin.gen.Accessor; 9 | 10 | @Mixin(ChunkDeltaUpdateS2CPacket.class) 11 | public interface IChunkDeltaUpdateS2CPacket { 12 | 13 | @Accessor 14 | ChunkSectionPos getSectionPos(); 15 | 16 | @Mutable 17 | @Accessor 18 | void setSectionPos(ChunkSectionPos sectionPos); 19 | 20 | @Mutable 21 | @Accessor 22 | void setPositions(short[] positions); 23 | 24 | @Mutable 25 | @Accessor 26 | void setBlockStates(BlockState[] blockStates); 27 | 28 | // @Mutable 29 | // @Accessor 30 | // void setNoLightingUpdates(boolean noLightingUpdates); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IChunkHolder.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.server.world.ChunkHolder; 4 | import net.minecraft.server.world.ServerChunkLoadingManager; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Invoker; 7 | 8 | import java.util.concurrent.Executor; 9 | 10 | @Mixin(ChunkHolder.class) 11 | public interface IChunkHolder { 12 | 13 | @Invoker 14 | void invokeUpdateFutures(ServerChunkLoadingManager chunkStorage, Executor executor); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IChunkTicket.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.server.world.ChunkTicket; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Invoker; 6 | 7 | @Mixin(ChunkTicket.class) 8 | public interface IChunkTicket { 9 | 10 | @Invoker("isExpired") 11 | boolean invokeIsExpired1(long currentTick); // compiler doing shit 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IClientConnection.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import io.netty.channel.Channel; 4 | import net.minecraft.network.ClientConnection; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(ClientConnection.class) 9 | public interface IClientConnection { 10 | 11 | @Accessor 12 | Channel getChannel(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IEntityTrackerEntry.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.entity.Entity; 4 | import net.minecraft.entity.TrackedPosition; 5 | import net.minecraft.server.network.EntityTrackerEntry; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | import org.spongepowered.asm.mixin.gen.Invoker; 9 | 10 | import java.util.List; 11 | 12 | @Mixin(EntityTrackerEntry.class) 13 | public interface IEntityTrackerEntry { 14 | 15 | @Invoker 16 | void invokeSyncEntityData(); 17 | 18 | @Accessor 19 | Entity getEntity(); 20 | 21 | @Accessor 22 | List getLastPassengers(); 23 | 24 | @Accessor 25 | void setLastPassengers(List lastPassengers); 26 | 27 | @Accessor 28 | TrackedPosition getTrackedPos(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IPointOfInterestSet.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.world.poi.PointOfInterestSet; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Invoker; 6 | 7 | @Mixin(PointOfInterestSet.class) 8 | public interface IPointOfInterestSet { 9 | 10 | @Invoker 11 | boolean invokeIsValid(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IServerChunkManager.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.server.world.ChunkTicketManager; 4 | import net.minecraft.server.world.ServerChunkManager; 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(ServerChunkManager.class) 10 | public interface IServerChunkManager { 11 | 12 | @Accessor 13 | ChunkTicketManager getTicketManager(); 14 | 15 | @Invoker 16 | boolean invokeUpdateChunks(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IServerCommandSource.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.server.command.CommandOutput; 4 | import net.minecraft.server.command.ServerCommandSource; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(ServerCommandSource.class) 9 | public interface IServerCommandSource { 10 | 11 | @Accessor 12 | CommandOutput getOutput(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IServerCommonNetworkHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Raknetify project, licensed under MIT. 3 | * 4 | * Copyright (c) 2022 ishland 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.ishland.vmp.mixins.access; 26 | 27 | import net.minecraft.network.ClientConnection; 28 | import net.minecraft.server.network.ServerCommonNetworkHandler; 29 | import org.spongepowered.asm.mixin.Mixin; 30 | import org.spongepowered.asm.mixin.gen.Accessor; 31 | 32 | @Mixin(ServerCommonNetworkHandler.class) 33 | public interface IServerCommonNetworkHandler { 34 | 35 | @Accessor 36 | ClientConnection getConnection(); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/ISpreadPlayersCommandPile.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.server.command.SpreadPlayersCommand; 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(SpreadPlayersCommand.Pile.class) 9 | public interface ISpreadPlayersCommandPile { 10 | 11 | @Accessor("x") 12 | double getX(); 13 | 14 | @Accessor("z") 15 | double getZ(); 16 | 17 | @Accessor("x") 18 | void setX(double x); 19 | 20 | @Accessor("z") 21 | void setZ(double z); 22 | 23 | @Invoker 24 | double invokeGetDistance(SpreadPlayersCommand.Pile other); 25 | 26 | @Invoker 27 | void invokeNormalize(); 28 | 29 | @Invoker 30 | double invokeAbsolute(); 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IThreadedAnvilChunkStorage.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.server.world.ChunkHolder; 6 | import net.minecraft.server.world.PlayerChunkWatchingManager; 7 | import net.minecraft.server.world.ServerChunkLoadingManager; 8 | import net.minecraft.server.world.ServerWorld; 9 | import net.minecraft.util.math.ChunkPos; 10 | import net.minecraft.util.thread.ThreadExecutor; 11 | import org.apache.commons.lang3.mutable.MutableObject; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.gen.Accessor; 14 | import org.spongepowered.asm.mixin.gen.Invoker; 15 | 16 | @Mixin(ServerChunkLoadingManager.class) 17 | public interface IThreadedAnvilChunkStorage { 18 | 19 | @Invoker 20 | ChunkHolder invokeGetCurrentChunkHolder(long pos); 21 | 22 | @Invoker 23 | ChunkHolder invokeGetChunkHolder(long pos); 24 | 25 | @Accessor 26 | ServerWorld getWorld(); 27 | 28 | @Accessor 29 | PlayerChunkWatchingManager getPlayerChunkWatchingManager(); 30 | 31 | @Accessor 32 | ThreadExecutor getMainThreadExecutor(); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IThreadedAnvilChunkStorageEntityTracker.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.entity.Entity; 4 | import net.minecraft.server.world.ServerChunkLoadingManager; 5 | import net.minecraft.util.math.ChunkSectionPos; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | import org.spongepowered.asm.mixin.gen.Invoker; 9 | 10 | @Mixin(ServerChunkLoadingManager.EntityTracker.class) 11 | public interface IThreadedAnvilChunkStorageEntityTracker { 12 | 13 | @Accessor 14 | Entity getEntity(); 15 | 16 | @Accessor 17 | ChunkSectionPos getTrackedSection(); 18 | 19 | @Accessor 20 | void setTrackedSection(ChunkSectionPos trackedSection); 21 | 22 | @Invoker 23 | int invokeGetMaxTrackDistance(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/IThreadedAnvilChunkStorageTicketManager.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.server.world.ServerChunkLoadingManager; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(ServerChunkLoadingManager.TicketManager.class) 8 | public interface IThreadedAnvilChunkStorageTicketManager { 9 | 10 | @Accessor 11 | ServerChunkLoadingManager getField_17443(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/access/ITrackedPosition.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.access; 2 | 3 | import net.minecraft.entity.TrackedPosition; 4 | import net.minecraft.util.math.Vec3d; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(TrackedPosition.class) 9 | public interface ITrackedPosition { 10 | 11 | @Accessor 12 | Vec3d getPos(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/carpet/MixinEntityPlayerMPFake.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.carpet; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import com.mojang.authlib.GameProfile; 5 | import net.minecraft.network.encryption.PlayerPublicKey; 6 | import net.minecraft.network.packet.c2s.common.SyncedClientOptions; 7 | import net.minecraft.server.MinecraftServer; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import net.minecraft.server.world.ServerChunkManager; 10 | import net.minecraft.server.world.ServerWorld; 11 | import net.minecraft.util.math.Vec3d; 12 | import org.jetbrains.annotations.Nullable; 13 | import org.spongepowered.asm.mixin.Dynamic; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.Pseudo; 16 | import org.spongepowered.asm.mixin.Unique; 17 | import org.spongepowered.asm.mixin.injection.At; 18 | import org.spongepowered.asm.mixin.injection.Redirect; 19 | 20 | import java.util.Optional; 21 | import java.util.concurrent.CompletableFuture; 22 | import java.util.function.Function; 23 | 24 | @Pseudo 25 | @Mixin(targets = "carpet.patches.EntityPlayerMPFake") 26 | public abstract class MixinEntityPlayerMPFake extends ServerPlayerEntity { 27 | 28 | public MixinEntityPlayerMPFake(MinecraftServer server, ServerWorld world, GameProfile profile, SyncedClientOptions arg) { 29 | super(server, world, profile, arg); 30 | } 31 | 32 | @Unique private double vmp_lastX = Double.NaN; 33 | @Unique private double vmp_lastY = Double.NaN; 34 | @Unique private double vmp_lastZ = Double.NaN; 35 | 36 | @SuppressWarnings("DefaultAnnotationParam") 37 | @Dynamic 38 | @Redirect(method = {"tick", "method_5773"}, remap = false, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkManager;updatePosition(Lnet/minecraft/server/network/ServerPlayerEntity;)V", remap = true)) 39 | private void redirectUpdatePosition(ServerChunkManager serverChunkManager, ServerPlayerEntity __unused) { 40 | final Vec3d pos = this.getPos(); 41 | if (pos.x != vmp_lastX || pos.y != vmp_lastY || pos.z != vmp_lastZ) { // only do update when position changes 42 | vmp_lastX = pos.x; 43 | vmp_lastY = pos.y; 44 | vmp_lastZ = pos.z; 45 | serverChunkManager.updatePosition(this); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/chunk/loading/MixinPointOfInterestStorage.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.chunk.loading; 2 | 3 | import com.ishland.vmp.common.chunk.loading.IPOIAsyncPreload; 4 | import com.ishland.vmp.common.chunk.loading.async_chunks_on_player_login.AsyncChunkLoadUtil; 5 | import com.ishland.vmp.mixins.access.IPointOfInterestSet; 6 | import com.mojang.datafixers.DataFixer; 7 | import com.mojang.datafixers.util.Pair; 8 | import com.mojang.serialization.Codec; 9 | import it.unimi.dsi.fastutil.longs.LongSet; 10 | import net.minecraft.datafixer.DataFixTypes; 11 | import net.minecraft.registry.DynamicRegistryManager; 12 | import net.minecraft.server.world.ChunkErrorHandler; 13 | import net.minecraft.server.world.ServerWorld; 14 | import net.minecraft.util.math.BlockPos; 15 | import net.minecraft.util.math.ChunkPos; 16 | import net.minecraft.util.math.ChunkSectionPos; 17 | import net.minecraft.world.HeightLimitView; 18 | import net.minecraft.world.chunk.ChunkStatus; 19 | import net.minecraft.world.poi.PointOfInterestSet; 20 | import net.minecraft.world.poi.PointOfInterestStorage; 21 | import net.minecraft.world.storage.ChunkPosKeyedStorage; 22 | import net.minecraft.world.storage.SerializingRegionBasedStorage; 23 | import org.spongepowered.asm.mixin.Final; 24 | import org.spongepowered.asm.mixin.Mixin; 25 | import org.spongepowered.asm.mixin.Shadow; 26 | 27 | import java.nio.file.Path; 28 | import java.util.concurrent.CompletableFuture; 29 | import java.util.function.BiFunction; 30 | import java.util.function.Function; 31 | 32 | @Mixin(PointOfInterestStorage.class) 33 | public abstract class MixinPointOfInterestStorage extends SerializingRegionBasedStorage implements IPOIAsyncPreload { 34 | 35 | @Shadow @Final private LongSet preloadedChunks; 36 | 37 | public MixinPointOfInterestStorage(ChunkPosKeyedStorage storageAccess, Codec codec, Function serializer, BiFunction deserializer, Function factory, DynamicRegistryManager registryManager, ChunkErrorHandler errorHandler, HeightLimitView world) { 38 | super(storageAccess, codec, serializer, deserializer, factory, registryManager, errorHandler, world); 39 | } 40 | 41 | @Override 42 | public CompletableFuture preloadChunksAtAsync(ServerWorld world, BlockPos pos, int radius) { 43 | if (!world.getServer().isOnThread()) { 44 | return CompletableFuture 45 | .supplyAsync(() -> preloadChunksAtAsync(world, pos, radius), world.getServer()) 46 | .thenCompose(Function.identity()); 47 | } 48 | final CompletableFuture[] futures = ChunkSectionPos.stream(new ChunkPos(pos), Math.floorDiv(radius, 16), this.world.getBottomSectionCoord(), this.world.getTopSectionCoord()) 49 | .map(sectionPos -> Pair.of(sectionPos, this.get(sectionPos.asLong()))) 50 | .filter(pair -> !(pair.getSecond()).map(pointOfInterestSet -> ((IPointOfInterestSet) pointOfInterestSet).invokeIsValid()).orElse(false)) 51 | .map(pair -> pair.getFirst().toChunkPos()) 52 | .filter(chunkPos -> !this.preloadedChunks.contains(chunkPos.toLong())) 53 | .map(chunkPos -> 54 | AsyncChunkLoadUtil.scheduleChunkLoadToStatus(world, chunkPos, ChunkStatus.EMPTY) 55 | .whenCompleteAsync((either, unused1) -> either.ifPresent(chunk -> this.preloadedChunks.add(chunk.getPos().toLong())), world.getServer()) 56 | ) 57 | .toArray(CompletableFuture[]::new); 58 | return CompletableFuture.allOf(futures); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/chunk/loading/async_chunk_on_player_login/MixinServerConfigurationNetworkHandler.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.chunk.loading.async_chunk_on_player_login; 2 | 3 | import com.google.common.base.Stopwatch; 4 | import com.ishland.vmp.common.chunk.loading.async_chunks_on_player_login.AsyncChunkLoadUtil; 5 | import com.ishland.vmp.common.config.Config; 6 | import com.ishland.vmp.mixins.access.IClientConnection; 7 | import com.ishland.vmp.mixins.access.IServerChunkManager; 8 | import com.ishland.vmp.mixins.access.IThreadedAnvilChunkStorage; 9 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 10 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 11 | import com.mojang.authlib.GameProfile; 12 | import com.mojang.serialization.Dynamic; 13 | import io.netty.channel.Channel; 14 | import net.minecraft.nbt.NbtOps; 15 | import net.minecraft.network.ClientConnection; 16 | import net.minecraft.network.DisconnectionInfo; 17 | import net.minecraft.network.listener.ServerConfigurationPacketListener; 18 | import net.minecraft.network.listener.TickablePacketListener; 19 | import net.minecraft.network.packet.Packet; 20 | import net.minecraft.network.packet.c2s.common.SyncedClientOptions; 21 | import net.minecraft.network.packet.s2c.common.DisconnectS2CPacket; 22 | import net.minecraft.registry.RegistryKey; 23 | import net.minecraft.server.MinecraftServer; 24 | import net.minecraft.server.PlayerManager; 25 | import net.minecraft.server.network.ConnectedClientData; 26 | import net.minecraft.server.network.JoinWorldTask; 27 | import net.minecraft.server.network.ServerCommonNetworkHandler; 28 | import net.minecraft.server.network.ServerConfigurationNetworkHandler; 29 | import net.minecraft.server.network.ServerPlayerConfigurationTask; 30 | import net.minecraft.server.network.ServerPlayerEntity; 31 | import net.minecraft.server.world.ChunkHolder; 32 | import net.minecraft.server.world.ChunkTicketType; 33 | import net.minecraft.server.world.ServerWorld; 34 | import net.minecraft.text.Text; 35 | import net.minecraft.util.Unit; 36 | import net.minecraft.util.math.ChunkPos; 37 | import net.minecraft.world.World; 38 | import net.minecraft.world.dimension.DimensionType; 39 | import org.slf4j.Logger; 40 | import org.spongepowered.asm.mixin.Final; 41 | import org.spongepowered.asm.mixin.Mixin; 42 | import org.spongepowered.asm.mixin.Shadow; 43 | import org.spongepowered.asm.mixin.Unique; 44 | import org.spongepowered.asm.mixin.injection.At; 45 | import org.spongepowered.asm.mixin.injection.Inject; 46 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 47 | 48 | import java.util.concurrent.CompletableFuture; 49 | import java.util.function.Consumer; 50 | 51 | @Mixin(ServerConfigurationNetworkHandler.class) 52 | public abstract class MixinServerConfigurationNetworkHandler extends ServerCommonNetworkHandler implements ServerConfigurationPacketListener, TickablePacketListener { 53 | 54 | @Shadow @Final private static Logger LOGGER; 55 | 56 | @Shadow public abstract boolean isConnectionOpen(); 57 | 58 | @Shadow @Final private GameProfile profile; 59 | 60 | @Shadow private SyncedClientOptions syncedOptions; 61 | 62 | @Shadow @Final private static Text INVALID_PLAYER_DATA_TEXT; 63 | @Unique 64 | private static final ChunkTicketType VMP_PLAYER_ASYNC_CHUNKS = ChunkTicketType.create("vmp_player_async_chunk", (unit, unit2) -> 0); 65 | 66 | @Unique 67 | private ChunkPos vmp$ticketHeld; 68 | 69 | @Unique 70 | private ServerWorld vmp$ticketHeldWorld; 71 | 72 | @Unique 73 | private ServerPlayerEntity vmp$heldPlayer; 74 | 75 | public MixinServerConfigurationNetworkHandler(MinecraftServer server, ClientConnection connection, ConnectedClientData clientData) { 76 | super(server, connection, clientData); 77 | } 78 | 79 | @Inject(method = "onDisconnected", at = @At("RETURN")) 80 | private void onDisconnect(DisconnectionInfo info, CallbackInfo ci) { 81 | vmp$dropTicket(); 82 | } 83 | 84 | @Unique 85 | private void vmp$dropTicket() { 86 | if (this.vmp$ticketHeld != null && this.vmp$ticketHeldWorld != null) { 87 | ((IServerChunkManager) this.vmp$ticketHeldWorld.getChunkManager()).getTicketManager().removeTicketWithLevel(VMP_PLAYER_ASYNC_CHUNKS, this.vmp$ticketHeld, 31, Unit.INSTANCE); 88 | this.vmp$ticketHeld = null; 89 | this.vmp$ticketHeldWorld = null; 90 | } 91 | } 92 | 93 | @WrapOperation(method = "onReady", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;createPlayer(Lcom/mojang/authlib/GameProfile;Lnet/minecraft/network/packet/c2s/common/SyncedClientOptions;)Lnet/minecraft/server/network/ServerPlayerEntity;")) 94 | private ServerPlayerEntity replacePlayer(PlayerManager instance, GameProfile profile, SyncedClientOptions syncedOptions, Operation original) { 95 | if (this.vmp$heldPlayer != null) { 96 | this.vmp$dropTicket(); 97 | return this.vmp$heldPlayer; 98 | } else { 99 | return original.call(instance, profile, syncedOptions); 100 | } 101 | } 102 | 103 | @WrapOperation(method = "pollTask", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerConfigurationTask;sendPacket(Ljava/util/function/Consumer;)V")) 104 | private void delayJoinWorld(ServerPlayerConfigurationTask instance, Consumer> packetConsumer, Operation original) { 105 | if (instance instanceof JoinWorldTask) { 106 | PlayerManager playerManager = this.server.getPlayerManager(); 107 | if (playerManager.getPlayer(this.profile.getId()) != null) { 108 | this.disconnect(PlayerManager.DUPLICATE_LOGIN_TEXT); 109 | return; 110 | } 111 | 112 | Text text = playerManager.checkCanJoin(this.connection.getAddress(), this.profile); 113 | if (text != null) { 114 | this.disconnect(text); 115 | return; 116 | } 117 | 118 | ServerPlayerEntity player = playerManager.createPlayer(this.profile, this.syncedOptions); 119 | this.vmp$heldPlayer = player; 120 | 121 | RegistryKey registryKey = playerManager.loadPlayerData(player) 122 | .flatMap(nbt -> DimensionType.worldFromDimensionNbt(new Dynamic<>(NbtOps.INSTANCE, nbt.get("Dimension"))).resultOrPartial(LOGGER::error)) 123 | .orElse(World.OVERWORLD); 124 | ServerWorld storedWorld = playerManager.getServer().getWorld(registryKey); 125 | ServerWorld actualWorld; 126 | if (storedWorld == null) { 127 | LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", registryKey); 128 | actualWorld = playerManager.getServer().getOverworld(); 129 | } else { 130 | actualWorld = storedWorld; 131 | } 132 | 133 | ChunkPos chunkPos = new ChunkPos(player.getBlockPos()); 134 | this.vmp$dropTicket(); 135 | this.vmp$ticketHeld = chunkPos; 136 | this.vmp$ticketHeldWorld = actualWorld; 137 | Stopwatch timing = Stopwatch.createStarted(); 138 | AsyncChunkLoadUtil.SEMAPHORE.acquire().thenApplyAsync(unused -> { 139 | try { 140 | ((IServerChunkManager) actualWorld.getChunkManager()).getTicketManager().addTicketWithLevel(VMP_PLAYER_ASYNC_CHUNKS, chunkPos, 31, Unit.INSTANCE); 141 | ((IServerChunkManager) actualWorld.getChunkManager()).invokeUpdateChunks(); 142 | final ChunkHolder chunkHolder = ((IThreadedAnvilChunkStorage) actualWorld.getChunkManager().chunkLoadingManager).invokeGetCurrentChunkHolder(chunkPos.toLong()); 143 | if (chunkHolder == null) { 144 | throw new IllegalStateException("Chunk not there when requested"); 145 | } 146 | return chunkHolder.getEntityTickingFuture().whenCompleteAsync((worldChunkOptionalChunk, throwable) -> { 147 | if (Config.SHOW_ASYNC_LOADING_MESSAGES) { 148 | LOGGER.info("Async chunk loading for player {} completed after {}", profile.getName(), timing); 149 | } 150 | Channel channel = ((IClientConnection) this.connection).getChannel(); 151 | 152 | if (channel == null || !channel.isOpen()) { 153 | return; 154 | } 155 | 156 | try { 157 | original.call(instance, packetConsumer); 158 | } catch (Throwable t1) { 159 | LOGGER.error("Couldn't place player in world", t1); 160 | this.connection.send(new DisconnectS2CPacket(INVALID_PLAYER_DATA_TEXT)); 161 | this.connection.disconnect(INVALID_PLAYER_DATA_TEXT); 162 | } 163 | }, ((IThreadedAnvilChunkStorage) actualWorld.getChunkManager().chunkLoadingManager).getMainThreadExecutor()); 164 | } catch (Throwable t) { 165 | LOGGER.warn("Failed to schedule chunkload for {} at {}", profile.getName(), chunkPos, t); 166 | try { 167 | original.call(instance, packetConsumer); 168 | } catch (Throwable t1) { 169 | LOGGER.error("Couldn't place player in world", t1); 170 | this.connection.send(new DisconnectS2CPacket(INVALID_PLAYER_DATA_TEXT)); 171 | this.connection.disconnect(INVALID_PLAYER_DATA_TEXT); 172 | } 173 | return CompletableFuture.completedFuture(null); 174 | } 175 | }, ((IThreadedAnvilChunkStorage) actualWorld.getChunkManager().chunkLoadingManager).getMainThreadExecutor()) 176 | .whenComplete((completableFuture, throwable) -> AsyncChunkLoadUtil.SEMAPHORE.release()); 177 | } else { 178 | original.call(instance, packetConsumer); 179 | } 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/chunk/loading/commands/MixinCommandFunctionManager.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.chunk.loading.commands; 2 | 3 | import net.minecraft.server.command.CommandOutput; 4 | import net.minecraft.server.command.ServerCommandSource; 5 | import net.minecraft.server.function.CommandFunctionManager; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 10 | 11 | @Mixin(CommandFunctionManager.class) 12 | public class MixinCommandFunctionManager { 13 | 14 | @Inject(method = "getScheduledCommandSource", at = @At("RETURN"), cancellable = true) 15 | private void onGetScheduledCommandSource(CallbackInfoReturnable cir) { 16 | cir.setReturnValue(cir.getReturnValue().withOutput(CommandOutput.DUMMY)); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/chunk/loading/commands/MixinSpreadPlayersCommand.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.chunk.loading.commands; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.ishland.vmp.common.chunk.loading.async_chunks_on_player_login.AsyncChunkLoadUtil; 5 | import com.ishland.vmp.mixins.access.IServerCommandSource; 6 | import com.ishland.vmp.mixins.access.ISpreadPlayersCommandPile; 7 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 8 | import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; 9 | import com.mojang.brigadier.exceptions.Dynamic4CommandExceptionType; 10 | import net.minecraft.entity.Entity; 11 | import net.minecraft.entity.player.PlayerEntity; 12 | import net.minecraft.scoreboard.AbstractTeam; 13 | import net.minecraft.server.MinecraftServer; 14 | import net.minecraft.server.ServerTask; 15 | import net.minecraft.server.command.CommandOutput; 16 | import net.minecraft.server.command.ServerCommandSource; 17 | import net.minecraft.server.command.SpreadPlayersCommand; 18 | import net.minecraft.server.rcon.RconCommandOutput; 19 | import net.minecraft.server.world.ServerWorld; 20 | import net.minecraft.text.Text; 21 | import net.minecraft.text.Texts; 22 | import net.minecraft.util.math.BlockPos; 23 | import net.minecraft.util.math.ChunkPos; 24 | import net.minecraft.util.math.Vec2f; 25 | import net.minecraft.util.math.random.Random; 26 | import org.spongepowered.asm.mixin.Final; 27 | import org.spongepowered.asm.mixin.Mixin; 28 | import org.spongepowered.asm.mixin.Shadow; 29 | import org.spongepowered.asm.mixin.Unique; 30 | import org.spongepowered.asm.mixin.injection.At; 31 | import org.spongepowered.asm.mixin.injection.Inject; 32 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 33 | 34 | import java.util.*; 35 | import java.util.concurrent.CompletableFuture; 36 | import java.util.concurrent.LinkedBlockingQueue; 37 | import java.util.concurrent.ThreadPoolExecutor; 38 | import java.util.concurrent.TimeUnit; 39 | import java.util.concurrent.atomic.AtomicBoolean; 40 | import java.util.function.Function; 41 | 42 | @Mixin(SpreadPlayersCommand.class) 43 | public abstract class MixinSpreadPlayersCommand { 44 | 45 | private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor( 46 | 1, 1, 47 | 10L, TimeUnit.SECONDS, 48 | new LinkedBlockingQueue<>(8) 49 | ); 50 | 51 | @Shadow 52 | @Final 53 | private static Dynamic2CommandExceptionType INVALID_HEIGHT_EXCEPTION; 54 | 55 | @Shadow 56 | protected static int getPileCountRespectingTeams(Collection entities) { 57 | throw new AbstractMethodError(); 58 | } 59 | 60 | @Shadow 61 | protected static SpreadPlayersCommand.Pile[] makePiles(net.minecraft.util.math.random.Random random, int count, double minX, double minZ, double maxX, double maxZ) { 62 | throw new AbstractMethodError(); 63 | } 64 | 65 | @Shadow 66 | @Final 67 | private static Dynamic4CommandExceptionType FAILED_TEAMS_EXCEPTION; 68 | 69 | @Shadow 70 | @Final 71 | private static Dynamic4CommandExceptionType FAILED_ENTITIES_EXCEPTION; 72 | 73 | @Inject(method = "execute", at = @At("HEAD"), cancellable = true) 74 | private static void execute( 75 | ServerCommandSource source, Vec2f center, float spreadDistance, float maxRange, int maxY, boolean respectTeams, Collection players, 76 | CallbackInfoReturnable cir 77 | ) throws CommandSyntaxException { 78 | final CommandOutput output = ((IServerCommandSource) source).getOutput(); 79 | if (!(output instanceof PlayerEntity) && !(output instanceof MinecraftServer) && !(output instanceof RconCommandOutput)) { 80 | return; 81 | } 82 | cir.cancel(); 83 | cir.setReturnValue(0); 84 | ServerWorld serverWorld = source.getWorld(); 85 | int i = serverWorld.getBottomY(); 86 | if (maxY < i) { 87 | throw INVALID_HEIGHT_EXCEPTION.create(maxY, i); 88 | } else { 89 | Random random = Random.create(); 90 | double d = center.x - maxRange; 91 | double e = center.y - maxRange; 92 | double f = center.x + maxRange; 93 | double g = center.y + maxRange; 94 | SpreadPlayersCommand.Pile[] piles = makePiles(random, respectTeams ? getPileCountRespectingTeams(players) : players.size(), d, e, f, g); 95 | EXECUTOR.execute(() -> { 96 | try { 97 | vmp$spread(center, spreadDistance, serverWorld, random, d, e, f, g, maxY, piles, respectTeams); 98 | } catch (CommandSyntaxException ex) { 99 | source.getServer().send(new ServerTask(0, () -> { 100 | source.sendError(Texts.toText(ex.getRawMessage())); 101 | })); 102 | } catch (Throwable t) { 103 | source.getServer().execute(() -> { 104 | source.sendError(Text.literal("An error occurred while spreading players, check console for details")); 105 | t.printStackTrace(); 106 | }); 107 | } 108 | double h = vmp$getMinDistance(players, serverWorld, piles, maxY, respectTeams); 109 | source.getServer().execute(() -> { 110 | source.sendFeedback( 111 | () -> Text.translatable( 112 | "commands.spreadplayers.success." + (respectTeams ? "teams" : "entities"), piles.length, center.x, center.y, String.format(Locale.ROOT, "%.2f", h) 113 | ), 114 | true 115 | ); 116 | }); 117 | }); 118 | cir.setReturnValue(piles.length); 119 | } 120 | } 121 | 122 | @Unique 123 | private static void vmp$spread( 124 | Vec2f center, 125 | double spreadDistance, 126 | ServerWorld world, 127 | Random random, 128 | double minX, 129 | double minZ, 130 | double maxX, 131 | double maxZ, 132 | int maxY, 133 | SpreadPlayersCommand.Pile[] piles, 134 | boolean respectTeams 135 | ) throws CommandSyntaxException { 136 | boolean bl = true; 137 | double d = Float.MAX_VALUE; 138 | 139 | int i; 140 | for (i = 0; i < 10000 && bl; ++i) { 141 | bl = false; 142 | d = Float.MAX_VALUE; 143 | 144 | for (int j = 0; j < piles.length; ++j) { 145 | SpreadPlayersCommand.Pile pile = piles[j]; 146 | int k = 0; 147 | SpreadPlayersCommand.Pile pile2 = new SpreadPlayersCommand.Pile(); 148 | 149 | for (int l = 0; l < piles.length; ++l) { 150 | if (j != l) { 151 | SpreadPlayersCommand.Pile pile3 = piles[l]; 152 | double e = ((ISpreadPlayersCommandPile) pile).invokeGetDistance(pile3); 153 | d = Math.min(e, d); 154 | if (e < spreadDistance) { 155 | ++k; 156 | ((ISpreadPlayersCommandPile) pile2).setX(((ISpreadPlayersCommandPile) pile2).getX() + (((ISpreadPlayersCommandPile) pile3).getX() - ((ISpreadPlayersCommandPile) pile).getX())); 157 | ((ISpreadPlayersCommandPile) pile2).setZ(((ISpreadPlayersCommandPile) pile2).getZ() + (((ISpreadPlayersCommandPile) pile3).getZ() - ((ISpreadPlayersCommandPile) pile).getZ())); 158 | } 159 | } 160 | } 161 | 162 | if (k > 0) { 163 | ((ISpreadPlayersCommandPile) pile2).setX(((ISpreadPlayersCommandPile) pile2).getX() / k); 164 | ((ISpreadPlayersCommandPile) pile2).setZ(((ISpreadPlayersCommandPile) pile2).getZ() / k); 165 | double f = ((ISpreadPlayersCommandPile) pile2).invokeAbsolute(); 166 | if (f > 0.0) { 167 | ((ISpreadPlayersCommandPile) pile2).invokeNormalize(); 168 | pile.subtract(pile2); 169 | } else { 170 | pile.setPileLocation(random, minX, minZ, maxX, maxZ); 171 | } 172 | 173 | bl = true; 174 | } 175 | 176 | if (pile.clamp(minX, minZ, maxX, maxZ)) { 177 | bl = true; 178 | } 179 | } 180 | 181 | if (!bl) { 182 | List> futures = new ArrayList<>(piles.length); 183 | AtomicBoolean result = new AtomicBoolean(false); 184 | for (SpreadPlayersCommand.Pile pile2 : piles) { 185 | ChunkPos pos = new ChunkPos(BlockPos.ofFloored(((ISpreadPlayersCommandPile) pile2).getX(), 0.0, ((ISpreadPlayersCommandPile) pile2).getZ())); 186 | final CompletableFuture future = 187 | CompletableFuture.supplyAsync(() -> AsyncChunkLoadUtil.scheduleChunkLoad(world, pos), world.getServer()) 188 | .thenCompose(Function.identity()) 189 | .whenCompleteAsync((unused, throwable) -> { 190 | if (!pile2.isSafe(world, maxY)) { 191 | pile2.setPileLocation(random, minX, minZ, maxX, maxZ); 192 | result.set(true); 193 | } 194 | }, world.getServer()) 195 | .exceptionally(throwable -> null) 196 | .thenRun(() -> { 197 | }); 198 | futures.add(future); 199 | } 200 | CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join(); 201 | bl = result.get(); 202 | } 203 | } 204 | 205 | if (d == Float.MAX_VALUE) { 206 | d = 0.0; 207 | } 208 | 209 | if (i >= 10000) { 210 | if (respectTeams) { 211 | throw FAILED_TEAMS_EXCEPTION.create(piles.length, center.x, center.y, String.format(Locale.ROOT, "%.2f", d)); 212 | } else { 213 | throw FAILED_ENTITIES_EXCEPTION.create(piles.length, center.x, center.y, String.format(Locale.ROOT, "%.2f", d)); 214 | } 215 | } 216 | } 217 | 218 | @Unique 219 | private static double vmp$getMinDistance( 220 | Collection entities, ServerWorld world, SpreadPlayersCommand.Pile[] piles, int maxY, boolean respectTeams 221 | ) { 222 | double d = 0.0; 223 | int i = 0; 224 | Map map = Maps.newHashMap(); 225 | 226 | List> futures = new ArrayList<>(piles.length); 227 | 228 | for (Entity entity : entities) { 229 | if (!entity.isAlive()) continue; 230 | SpreadPlayersCommand.Pile pile; 231 | if (respectTeams) { 232 | AbstractTeam abstractTeam = entity instanceof PlayerEntity ? entity.getScoreboardTeam() : null; 233 | if (!map.containsKey(abstractTeam)) { 234 | map.put(abstractTeam, piles[i++]); 235 | } 236 | 237 | pile = map.get(abstractTeam); 238 | } else { 239 | pile = piles[i++]; 240 | } 241 | 242 | ChunkPos pos = new ChunkPos(BlockPos.ofFloored(((ISpreadPlayersCommandPile) pile).getX(), 0.0, ((ISpreadPlayersCommandPile) pile).getZ())); 243 | final CompletableFuture future = 244 | CompletableFuture.supplyAsync(() -> AsyncChunkLoadUtil.scheduleChunkLoad(world, pos), world.getServer()) 245 | .thenCompose(Function.identity()) 246 | .whenCompleteAsync((unused, throwable) -> { 247 | entity.teleport(world, Math.floor(((ISpreadPlayersCommandPile) pile).getX()) + 0.5, pile.getY(world, maxY), Math.floor(((ISpreadPlayersCommandPile) pile).getZ()) + 0.5, Set.of(), entity.getYaw(), entity.getPitch(), true); 248 | }, world.getServer()) 249 | .exceptionally(throwable -> null) 250 | .thenRun(() -> { 251 | }); 252 | futures.add(future); 253 | 254 | double e = Double.MAX_VALUE; 255 | 256 | for (SpreadPlayersCommand.Pile pile2 : piles) { 257 | if (pile != pile2) { 258 | double f = ((ISpreadPlayersCommandPile) pile).invokeGetDistance(pile2); 259 | e = Math.min(f, e); 260 | } 261 | } 262 | 263 | d += e; 264 | } 265 | 266 | CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join(); 267 | 268 | return entities.size() < 2 ? 0.0 : d / (double) entities.size(); 269 | } 270 | 271 | } 272 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/chunk/loading/package-info.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.chunk.loading; -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/chunk/package-info.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.chunk; -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/chunk/ticking/MixinServerWorld.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.chunk.ticking; 2 | 3 | import net.minecraft.server.world.ServerWorld; 4 | import net.minecraft.util.math.BlockPos; 5 | import net.minecraft.world.Heightmap; 6 | import net.minecraft.world.chunk.WorldChunk; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Redirect; 10 | 11 | @Mixin(ServerWorld.class) 12 | public class MixinServerWorld { 13 | 14 | // @Redirect(method = "tickChunk", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;getTopPosition(Lnet/minecraft/world/Heightmap$Type;Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/util/math/BlockPos;")) 15 | // private BlockPos redirectGetTopPosition(ServerWorld instance, Heightmap.Type type, BlockPos blockPos, WorldChunk chunk, int randomTickSpeed) { 16 | // return blockPos.withY(chunk.sampleHeightmap(type, blockPos.getX(), blockPos.getZ()) + 1); 17 | // } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/core/package-info.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.core; -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/entity/move_zero_velocity/MixinEntity.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.entity.move_zero_velocity; 2 | 3 | import net.minecraft.entity.Entity; 4 | import net.minecraft.entity.MovementType; 5 | import net.minecraft.util.math.Box; 6 | import net.minecraft.util.math.Vec3d; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.Unique; 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 | @Mixin(Entity.class) 15 | public class MixinEntity { 16 | 17 | @Shadow private Box boundingBox; 18 | @Unique 19 | private boolean boundingBoxChanged = false; 20 | 21 | @Inject(method = "move", at = @At("HEAD"), cancellable = true) 22 | private void onMove(MovementType movementType, Vec3d movement, CallbackInfo ci) { 23 | if (!boundingBoxChanged && movement.equals(Vec3d.ZERO)) { 24 | ci.cancel(); 25 | boundingBoxChanged = false; 26 | } 27 | } 28 | 29 | @Inject(method = "setBoundingBox", at = @At("HEAD")) 30 | private void onBoundingBoxChanged(Box boundingBox, CallbackInfo ci) { 31 | if (!this.boundingBox.equals(boundingBox)) boundingBoxChanged = true; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/entity/package-info.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.entity; -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/entitytracker/MixinThreadedAnvilChunkStorage.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.entitytracker; 2 | 3 | import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; 4 | import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 5 | import net.minecraft.server.world.ServerChunkLoadingManager; 6 | import org.spongepowered.asm.mixin.Final; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Mutable; 9 | import org.spongepowered.asm.mixin.Shadow; 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 | @Mixin(ServerChunkLoadingManager.class) 15 | public class MixinThreadedAnvilChunkStorage { 16 | 17 | @Mutable 18 | @Shadow @Final private Int2ObjectMap entityTrackers; 19 | 20 | @Inject(method = "", at = @At("RETURN")) 21 | private void onInit(CallbackInfo info) { 22 | this.entityTrackers = new Int2ObjectLinkedOpenHashMap<>(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/entitytracker/MixinThreadedAnvilChunkStorageEntityTracker.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.entitytracker; 2 | 3 | import com.ishland.vmp.mixins.access.IThreadedAnvilChunkStorage; 4 | import net.minecraft.server.world.ServerChunkLoadingManager; 5 | import org.spongepowered.asm.mixin.Final; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Shadow; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Redirect; 11 | 12 | @Mixin(ServerChunkLoadingManager.EntityTracker.class) 13 | public abstract class MixinThreadedAnvilChunkStorageEntityTracker { 14 | 15 | @Shadow @Final private ServerChunkLoadingManager field_18245; 16 | 17 | @Shadow protected abstract int getMaxTrackDistance(); 18 | 19 | @Unique 20 | private int lastDistanceUpdate = 0; 21 | 22 | @Unique 23 | private int cachedMaxDistance = 0; 24 | 25 | @Redirect(method = "updateTrackedStatus(Lnet/minecraft/server/network/ServerPlayerEntity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkLoadingManager$EntityTracker;getMaxTrackDistance()I")) 26 | private int redirectGetMaxTrackDistance(ServerChunkLoadingManager.EntityTracker instance) { 27 | final int ticks = ((IThreadedAnvilChunkStorage) field_18245).getWorld().getServer().getTicks(); 28 | if (lastDistanceUpdate != ticks || cachedMaxDistance == 0) { 29 | final int maxTrackDistance = this.getMaxTrackDistance(); 30 | this.cachedMaxDistance = maxTrackDistance; 31 | this.lastDistanceUpdate = ticks; 32 | return maxTrackDistance; 33 | } 34 | return this.cachedMaxDistance; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/general/biome_access/MixinBiomeAccess.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.general.biome_access; 2 | 3 | import net.minecraft.registry.entry.RegistryEntry; 4 | import net.minecraft.util.math.BlockPos; 5 | import net.minecraft.util.math.MathHelper; 6 | import net.minecraft.world.biome.Biome; 7 | import net.minecraft.world.biome.source.BiomeAccess; 8 | import org.spongepowered.asm.mixin.Final; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Overwrite; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | 13 | @Mixin(BiomeAccess.class) 14 | public class MixinBiomeAccess { 15 | 16 | @Shadow @Final private long seed; 17 | 18 | @Shadow @Final private BiomeAccess.Storage storage; 19 | 20 | /** 21 | * @author ishland 22 | * @reason optimize 23 | */ 24 | @Overwrite 25 | public RegistryEntry getBiome(BlockPos pos) { 26 | final int var0 = pos.getX() - 2; 27 | final int var1 = pos.getY() - 2; 28 | final int var2 = pos.getZ() - 2; 29 | final int var3 = var0 >> 2; 30 | final int var4 = var1 >> 2; 31 | final int var5 = var2 >> 2; 32 | final double var6 = (double) (var0 & 3) / 4.0; 33 | final double var7 = (double) (var1 & 3) / 4.0; 34 | final double var8 = (double) (var2 & 3) / 4.0; 35 | int var9 = 0; 36 | double var10 = Double.POSITIVE_INFINITY; 37 | 38 | for (int var11 = 0; var11 < 8; ++var11) { 39 | boolean var12 = (var11 & 4) == 0; 40 | boolean var13 = (var11 & 2) == 0; 41 | boolean var14 = (var11 & 1) == 0; 42 | long var15 = var12 ? var3 : var3 + 1; 43 | long var16 = var13 ? var4 : var4 + 1; 44 | long var17 = var14 ? var5 : var5 + 1; 45 | double var18 = var12 ? var6 : var6 - 1.0; 46 | double var19 = var13 ? var7 : var7 - 1.0; 47 | double var20 = var14 ? var8 : var8 - 1.0; 48 | long var21 = this.seed * (this.seed * 6364136223846793005L + 1442695040888963407L) + var15; 49 | var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var16; 50 | var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var17; 51 | var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var15; 52 | var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var16; 53 | var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var17; 54 | double var22 = (double)((var21 >> 24) & 1023) / 1024.0; 55 | double var23 = (var22 - 0.5) * 0.9; 56 | var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + this.seed; 57 | double var24 = (double)((var21 >> 24) & 1023) / 1024.0; 58 | double var25 = (var24 - 0.5) * 0.9; 59 | var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + this.seed; 60 | double var26 = (double)((var21 >> 24) & 1023) / 1024.0; 61 | double var27 = (var26 - 0.5) * 0.9; 62 | double var28 = MathHelper.square(var20 + var27) + MathHelper.square(var19 + var25) + MathHelper.square(var18 + var23); 63 | if (var10 > var28) { 64 | var9 = var11; 65 | var10 = var28; 66 | } 67 | } 68 | 69 | int resX = (var9 & 4) == 0 ? var3 : var3 + 1; 70 | int resY = (var9 & 2) == 0 ? var4 : var4 + 1; 71 | int resZ = (var9 & 1) == 0 ? var5 : var5 + 1; 72 | 73 | return this.storage.getBiomeForNoiseGen(resX, resY, resZ); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/general/biome_access/fast_chunk_access/MixinWorldView.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.general.biome_access.fast_chunk_access; 2 | 3 | import com.ishland.vmp.mixins.access.IThreadedAnvilChunkStorage; 4 | import com.mojang.datafixers.util.Either; 5 | import net.minecraft.server.world.ChunkHolder; 6 | import net.minecraft.server.world.OptionalChunk; 7 | import net.minecraft.server.world.ServerWorld; 8 | import net.minecraft.util.math.ChunkPos; 9 | import net.minecraft.world.WorldView; 10 | import net.minecraft.world.chunk.Chunk; 11 | import net.minecraft.world.chunk.ChunkStatus; 12 | import net.minecraft.world.chunk.WorldChunk; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Redirect; 16 | 17 | import java.util.concurrent.CompletableFuture; 18 | 19 | @Mixin(WorldView.class) 20 | public interface MixinWorldView { 21 | 22 | @Redirect(method = "getBiomeForNoiseGen", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/WorldView;getChunk(IILnet/minecraft/world/chunk/ChunkStatus;Z)Lnet/minecraft/world/chunk/Chunk;")) 23 | private Chunk redirectBiomeChunk(WorldView instance, int x, int z, ChunkStatus chunkStatus, boolean create) { 24 | if (!create && instance instanceof ServerWorld world) { 25 | final ChunkHolder holder = ((IThreadedAnvilChunkStorage) world.getChunkManager().chunkLoadingManager).invokeGetChunkHolder(ChunkPos.toLong(x, z)); 26 | if (holder != null) { 27 | final CompletableFuture> future = holder.getAccessibleFuture(); 28 | final OptionalChunk either = future.getNow(null); 29 | if (either != null) { 30 | final WorldChunk chunk = either.orElse(null); 31 | if (chunk != null) return chunk; 32 | } 33 | } 34 | } 35 | return instance.getChunk(x, z, chunkStatus, create); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/general/cache_ops/package-info.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.general.cache_ops; -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/general/collections/MixinTypeFilterableList.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.general.collections; 2 | 3 | import com.ishland.vmp.common.general.collections.ITypeFilterableList; 4 | import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; 5 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 6 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 7 | import net.minecraft.util.collection.TypeFilterableList; 8 | import org.objectweb.asm.Opcodes; 9 | import org.spongepowered.asm.mixin.Final; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Mutable; 12 | import org.spongepowered.asm.mixin.Overwrite; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Redirect; 16 | 17 | import java.util.AbstractCollection; 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.Collections; 21 | import java.util.HashMap; 22 | import java.util.HashSet; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Set; 26 | import java.util.stream.Collectors; 27 | 28 | @Mixin(value = TypeFilterableList.class, priority = 1005) // priority compatibility hack for lithium 29 | public abstract class MixinTypeFilterableList extends AbstractCollection implements ITypeFilterableList { 30 | 31 | @Mutable 32 | @Shadow @Final private Map, List> elementsByType; 33 | 34 | @Mutable 35 | @Shadow @Final private List allElements; 36 | 37 | @Shadow @Final private Class elementType; 38 | 39 | @Redirect(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/util/collection/TypeFilterableList;elementsByType:Ljava/util/Map;", opcode = Opcodes.PUTFIELD)) 40 | private void redirectSetElementsByType(TypeFilterableList instance, Map, List> value) { 41 | this.elementsByType = new Object2ObjectLinkedOpenHashMap<>(); 42 | } 43 | 44 | @Redirect(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/util/collection/TypeFilterableList;allElements:Ljava/util/List;", opcode = Opcodes.PUTFIELD)) 45 | private void redirectSetAllElements(TypeFilterableList instance, List value) { 46 | this.allElements = new ObjectArrayList<>(); 47 | } 48 | 49 | @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;", remap = false)) 50 | private HashMap redirectNewHashMap() { 51 | return null; // avoid unnecessary alloc 52 | } 53 | 54 | @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Lists;newArrayList()Ljava/util/ArrayList;", remap = false)) 55 | private ArrayList redirectNewArrayList() { 56 | return null; 57 | } 58 | 59 | @Override 60 | public Object[] getBackingArray() { 61 | return ((ObjectArrayList) this.allElements).elements(); 62 | } 63 | 64 | /** 65 | * @author ishland 66 | * @reason use fastutil array list for faster iteration & use array for filtering iteration 67 | */ 68 | @Overwrite 69 | public Collection getAllOfType(Class type) { 70 | List cached = this.elementsByType.get(type); 71 | if (cached != null) return (Collection) cached; 72 | 73 | if (!this.elementType.isAssignableFrom(type)) { 74 | throw new IllegalArgumentException("Don't know how to search for " + type); 75 | } else { 76 | List list = this.elementsByType.computeIfAbsent(type, 77 | typeClass -> { 78 | ObjectArrayList ts = new ObjectArrayList<>(this.allElements.size()); 79 | for (Object _allElement : ((ObjectArrayList) this.allElements).elements()) { 80 | if (typeClass.isInstance(_allElement)) { 81 | ts.add((T) _allElement); 82 | } 83 | } 84 | return ts; 85 | } 86 | ); 87 | return (Collection) list; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/general/no_locking/MixinPalettedContainer.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.general.no_locking; 2 | 3 | import net.minecraft.world.chunk.PalettedContainer; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.Overwrite; 6 | 7 | @Mixin(PalettedContainer.class) 8 | public class MixinPalettedContainer { 9 | 10 | /** 11 | * @author ishland 12 | * @reason removes locking 13 | */ 14 | @Overwrite 15 | public void lock() { 16 | // no-op 17 | } 18 | 19 | /** 20 | * @author ishland 21 | * @reason removes locking 22 | */ 23 | @Overwrite 24 | public void unlock() { 25 | // no-op 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/general/package-info.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.general; -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/general/spawn_density_cap/MixinSpawnDensityCapperDensityCap.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.general.spawn_density_cap; 2 | 3 | import com.ishland.vmp.common.general.spawn_density_cap.SpawnDensityCapperDensityCapDelegate; 4 | import it.unimi.dsi.fastutil.objects.Object2IntMap; 5 | import net.minecraft.entity.SpawnGroup; 6 | import net.minecraft.world.SpawnDensityCapper; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Mutable; 10 | import org.spongepowered.asm.mixin.Overwrite; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 15 | 16 | @Mixin(SpawnDensityCapper.DensityCap.class) 17 | public class MixinSpawnDensityCapperDensityCap { 18 | @Mutable 19 | @Shadow @Final private Object2IntMap spawnGroupsToDensity; 20 | private final int[] spawnGroupDensities = new int[SpawnGroup.values().length]; 21 | 22 | @Inject(method = "", at = @At("RETURN")) 23 | private void onInit(CallbackInfo ci) { 24 | this.spawnGroupsToDensity = SpawnDensityCapperDensityCapDelegate.delegateSpawnGroupDensities(spawnGroupDensities); 25 | } 26 | 27 | /** 28 | * @author ishland 29 | * @reason opt: replace with array access 30 | */ 31 | @Overwrite 32 | public void increaseDensity(SpawnGroup spawnGroup) { 33 | this.spawnGroupDensities[spawnGroup.ordinal()] ++; 34 | } 35 | 36 | /** 37 | * @author ishland 38 | * @reason opt: replace with array access 39 | */ 40 | @Overwrite 41 | public boolean canSpawn(SpawnGroup spawnGroup) { 42 | return this.spawnGroupDensities[spawnGroup.ordinal()] < spawnGroup.getCapacity(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/networking/avoid_deadlocks/MixinClientConnection.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.networking.avoid_deadlocks; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import net.minecraft.network.ClientConnection; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Shadow; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Redirect; 11 | 12 | @Mixin(ClientConnection.class) 13 | public class MixinClientConnection { 14 | 15 | @Shadow private Channel channel; 16 | @Unique 17 | private volatile boolean isClosing = false; 18 | 19 | @Redirect(method = "disconnect(Lnet/minecraft/network/DisconnectionInfo;)V", at = @At(value = "INVOKE", target = "Lio/netty/channel/ChannelFuture;awaitUninterruptibly()Lio/netty/channel/ChannelFuture;", remap = false)) 20 | private ChannelFuture noDisconnectWait(ChannelFuture instance) { 21 | isClosing = true; 22 | // if (instance.channel().eventLoop().inEventLoop()) { 23 | // return instance; // no-op 24 | // } else { 25 | // return instance.awaitUninterruptibly(); 26 | // } 27 | return instance; 28 | } 29 | 30 | @Redirect(method = "*", at = @At(value = "INVOKE", target = "Lio/netty/channel/Channel;isOpen()Z", remap = false)) 31 | private boolean redirectIsOpen(Channel instance) { 32 | return this.channel != null && (this.channel.isOpen() && !this.isClosing); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/networking/eventloops/MixinClientConnection.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.networking.eventloops; 2 | 3 | import com.ishland.vmp.common.networking.eventloops.VMPEventLoops; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelConfig; 6 | import io.netty.channel.ChannelPromise; 7 | import io.netty.channel.EventLoopGroup; 8 | import net.minecraft.network.ClientConnection; 9 | import net.minecraft.network.NetworkSide; 10 | import net.minecraft.network.NetworkState; 11 | import net.minecraft.network.listener.PacketListener; 12 | import org.jetbrains.annotations.Nullable; 13 | import org.objectweb.asm.Opcodes; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.Shadow; 16 | import org.spongepowered.asm.mixin.Unique; 17 | import org.spongepowered.asm.mixin.injection.At; 18 | import org.spongepowered.asm.mixin.injection.Inject; 19 | import org.spongepowered.asm.mixin.injection.Redirect; 20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 21 | 22 | @Mixin(ClientConnection.class) 23 | public abstract class MixinClientConnection { 24 | 25 | @Shadow private Channel channel; 26 | 27 | @Shadow private volatile @Nullable PacketListener packetListener; 28 | 29 | @Shadow public abstract NetworkSide getSide(); 30 | 31 | @Inject(method = "transitionInbound", at = @At(value = "RETURN")) 32 | private void onSetState(NetworkState state, PacketListener listener, CallbackInfo ci) { 33 | // if (this.channel.config() == instance) { 34 | // final EventLoopGroup group = VMPEventLoops.getEventLoopGroup(this.channel, state); 35 | // if (group != null) { 36 | // reregister(group); 37 | // return instance; 38 | // } else { 39 | // return instance.setAutoRead(b); 40 | // } 41 | // } else { 42 | // return instance.setAutoRead(b); 43 | // } 44 | if (this.getSide() == NetworkSide.CLIENTBOUND) { 45 | return; 46 | } 47 | final EventLoopGroup group = VMPEventLoops.getEventLoopGroup(this.channel, state.id()); 48 | if (group != null) { 49 | vmp$reregister(group); 50 | } 51 | } 52 | 53 | @Unique 54 | private boolean isReregistering = false; 55 | 56 | @Unique 57 | private EventLoopGroup pendingReregistration = null; 58 | 59 | @Unique 60 | private synchronized void vmp$reregister(EventLoopGroup group) { 61 | if (isReregistering) { 62 | pendingReregistration = group; 63 | return; 64 | } 65 | 66 | ChannelPromise promise = this.channel.newPromise(); 67 | this.channel.config().setAutoRead(false); 68 | isReregistering = true; 69 | // System.out.println("Deregistering " + this.channel); 70 | this.channel.deregister().addListener(future -> { 71 | if (future.isSuccess()) { 72 | // System.out.println("Reregistering " + this.channel); 73 | group.register(promise); 74 | } else { 75 | promise.setFailure(new RuntimeException("Failed to deregister channel", future.cause())); 76 | } 77 | }); 78 | promise.addListener(future -> { 79 | synchronized (this) { 80 | isReregistering = false; 81 | if (future.isSuccess()) { 82 | // System.out.println("Reregistered " + this.channel); 83 | this.channel.config().setAutoRead(true); 84 | } else { 85 | this.channel.pipeline().fireExceptionCaught(future.cause()); 86 | } 87 | if (pendingReregistration != null) { 88 | vmp$reregister(pendingReregistration); 89 | pendingReregistration = null; 90 | } 91 | } 92 | }); 93 | if (!promise.channel().eventLoop().inEventLoop()) { 94 | promise.syncUninterruptibly(); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/networking/eventloops/MixinServerNetworkIo.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.networking.eventloops; 2 | 3 | import net.minecraft.server.ServerNetworkIo; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.Constant; 6 | import org.spongepowered.asm.mixin.injection.ModifyConstant; 7 | 8 | @Mixin(ServerNetworkIo.class) 9 | public class MixinServerNetworkIo { 10 | 11 | @ModifyConstant(method = {"method_14348", "method_14349"}, constant = @Constant(intValue = 0), require = 2) 12 | private static int modifyAcceptorThreadCount(int constant) { 13 | if (constant == 0) { 14 | return 1; // only 1 thread for acceptor 15 | } else { 16 | return constant; 17 | } 18 | } 19 | 20 | @ModifyConstant(method = "method_14348", constant = @Constant(stringValue = "Netty Server IO #%d")) 21 | private static String modifyNioThreadName(String constant) { 22 | if (constant.equals("Netty Server IO #%d")) { 23 | return "Netty Acceptor IO Thread"; 24 | } else { 25 | return constant; 26 | } 27 | } 28 | 29 | @ModifyConstant(method = "method_14349", constant = @Constant(stringValue = "Netty Epoll Server IO #%d")) 30 | private static String modifyEpollThreadName(String constant) { 31 | if (constant.equals("Netty Epoll Server IO #%d")) { 32 | return "Netty Epoll Acceptor IO Thread"; 33 | } else { 34 | return constant; 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/networking/no_flush/MixinClientConnection.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.networking.no_flush; 2 | 3 | import io.netty.channel.Channel; 4 | import net.minecraft.network.ClientConnection; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Redirect; 8 | 9 | @Mixin(ClientConnection.class) 10 | public class MixinClientConnection { 11 | 12 | @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lio/netty/channel/Channel;flush()Lio/netty/channel/Channel;")) 13 | private Channel dontFlush(Channel instance) { 14 | return instance; // no-op 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/MixinChunkFilter.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching; 2 | 3 | import net.minecraft.server.network.ChunkFilter; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.Overwrite; 6 | 7 | @Mixin(ChunkFilter.class) 8 | public interface MixinChunkFilter { 9 | 10 | /** 11 | * @author ishland 12 | * @reason use chebyshev distance 13 | */ 14 | @Overwrite 15 | static boolean isWithinDistance(int centerX, int centerZ, int viewDistance, int x, int z, boolean includeEdge) { 16 | int actualViewDistance = viewDistance + (includeEdge ? 1 : 0); 17 | int xDistance = Math.abs(centerX - x); 18 | int zDistance = Math.abs(centerZ - z); 19 | return xDistance <= actualViewDistance && zDistance <= actualViewDistance; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/MixinServerPlayerEntity.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching; 2 | 3 | import com.ishland.vmp.common.chunkwatching.PlayerClientVDTracking; 4 | import net.minecraft.network.packet.c2s.common.SyncedClientOptions; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Unique; 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(ServerPlayerEntity.class) 13 | public class MixinServerPlayerEntity implements PlayerClientVDTracking { 14 | 15 | @Unique 16 | private boolean vdChanged = false; 17 | 18 | @Unique 19 | private int clientVD = 2; 20 | 21 | @Inject(method = "setClientOptions", at = @At("HEAD")) 22 | private void onClientSettingsChanged(SyncedClientOptions packet, CallbackInfo ci) { 23 | final int currentVD = packet.viewDistance(); 24 | if (currentVD != this.clientVD) this.vdChanged = true; 25 | this.clientVD = Math.max(2, currentVD); 26 | } 27 | 28 | @Inject(method = "copyFrom", at = @At("RETURN")) 29 | private void onPlayerCopy(ServerPlayerEntity oldPlayer, boolean alive, CallbackInfo ci) { 30 | this.clientVD = ((PlayerClientVDTracking) oldPlayer).getClientViewDistance(); 31 | this.vdChanged = true; 32 | } 33 | 34 | @Unique 35 | @Override 36 | public boolean isClientViewDistanceChanged() { 37 | return this.vdChanged; 38 | } 39 | 40 | @Unique 41 | @Override 42 | public int getClientViewDistance() { 43 | this.vdChanged = false; 44 | return this.clientVD; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/MixinTACSCancelSending.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching; 2 | 3 | import net.minecraft.server.network.ChunkFilter; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.server.world.PlayerChunkWatchingManager; 6 | import net.minecraft.server.world.ServerChunkLoadingManager; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Overwrite; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Redirect; 11 | 12 | import java.util.Collections; 13 | import java.util.Set; 14 | 15 | @Mixin(value = ServerChunkLoadingManager.class, priority = 1005) 16 | public class MixinTACSCancelSending { 17 | 18 | // @Redirect(method = "updatePosition", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkLoadingManager;sendWatchPackets(Lnet/minecraft/server/network/ServerPlayerEntity;)V")) 19 | // private void beforeWatchPacketsOnMoving(ThreadedAnvilChunkStorage instance, ServerPlayerEntity player) { 20 | // // Stop packet sending, handled by distance map 21 | // } 22 | 23 | @Redirect(method = "setViewDistance", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/PlayerChunkWatchingManager;getPlayersWatchingChunk()Ljava/util/Set;")) 24 | private Set redirectWatchPacketsOnChangingVD(PlayerChunkWatchingManager instance) { 25 | return Collections.emptySet(); // Stop packet sending, handled by distance map 26 | } 27 | 28 | // @Redirect(method = "handlePlayerAddedOrRemoved", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkLoadingManager;sendWatchPackets(Lnet/minecraft/server/network/ServerPlayerEntity;)V")) 29 | // private void redirectWatchPacketOnPlayerChanges0(ThreadedAnvilChunkStorage instance, ServerPlayerEntity player) { 30 | // // Stop packet sending, handled by distance map 31 | // } 32 | 33 | @Redirect(method = "handlePlayerAddedOrRemoved", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;setChunkFilter(Lnet/minecraft/server/network/ChunkFilter;)V")) 34 | private void redirectChunkFilterSet(ServerPlayerEntity instance, ChunkFilter chunkFilter) { 35 | // Stop packet sending, handled by distance map 36 | } 37 | 38 | // @Redirect(method = "handlePlayerAddedOrRemoved", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkLoadingManager;sendWatchPackets(Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/server/network/ChunkFilter;)V")) 39 | // private void redirectWatchPacketOnPlayerChanges1(ThreadedAnvilChunkStorage instance, ServerPlayerEntity player, ChunkFilter chunkFilter) { 40 | // // Stop packet sending, handled by distance map 41 | // } 42 | 43 | /** 44 | * @author ishland 45 | * @reason Stop packet sending, handled by distance map 46 | */ 47 | @Overwrite 48 | private void sendWatchPackets(ServerPlayerEntity player) { 49 | // no-op 50 | } 51 | 52 | /** 53 | * @author ishland 54 | * @reason Stop packet sending, handled by distance map 55 | */ 56 | @Overwrite 57 | private void sendWatchPackets(ServerPlayerEntity player, ChunkFilter chunkFilter) { 58 | // no-op 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/MixinTACSCancelSendingKrypton.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching; 2 | 3 | import net.minecraft.server.world.ServerChunkLoadingManager; 4 | import org.spongepowered.asm.mixin.Dynamic; 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 | @Mixin(ServerChunkLoadingManager.class) 11 | public class MixinTACSCancelSendingKrypton { 12 | 13 | @Dynamic("Compatibility hack for krypton") 14 | @Inject(method = { 15 | "sendChunks(Lnet/minecraft/util/math/ChunkSectionPos;Lnet/minecraft/server/network/ServerPlayerEntity;)V", 16 | "sendSpiralChunkWatchPackets(Lnet/minecraft/server/network/ServerPlayerEntity;)V", 17 | "unloadChunks(Lnet/minecraft/server/network/ServerPlayerEntity;III)V", 18 | "sendChunkWatchPackets(Lnet/minecraft/util/math/ChunkSectionPos;Lnet/minecraft/server/network/ServerPlayerEntity;)V" 19 | }, at = @At("HEAD"), cancellable = true, require = 0) 20 | private void preventExtraSendChunks(CallbackInfo ci) { 21 | ci.cancel(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/MixinThreadedAnvilChunkStorage.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.ishland.vmp.common.chunkwatching.AreaPlayerChunkWatchingManager; 5 | import com.ishland.vmp.common.config.Config; 6 | import com.ishland.vmp.common.playerwatching.TACSExtension; 7 | import net.minecraft.network.packet.s2c.play.ChunkRenderDistanceCenterS2CPacket; 8 | import net.minecraft.server.network.ChunkFilter; 9 | import net.minecraft.server.network.ServerPlayerEntity; 10 | import net.minecraft.server.world.PlayerChunkWatchingManager; 11 | import net.minecraft.server.world.ServerChunkLoadingManager; 12 | import net.minecraft.util.math.ChunkPos; 13 | import net.minecraft.util.math.ChunkSectionPos; 14 | import org.slf4j.Logger; 15 | import org.spongepowered.asm.mixin.Final; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Overwrite; 18 | import org.spongepowered.asm.mixin.Shadow; 19 | import org.spongepowered.asm.mixin.Unique; 20 | import org.spongepowered.asm.mixin.injection.At; 21 | import org.spongepowered.asm.mixin.injection.Inject; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 23 | 24 | import java.util.List; 25 | 26 | @Mixin(ServerChunkLoadingManager.class) 27 | public abstract class MixinThreadedAnvilChunkStorage implements TACSExtension { 28 | 29 | @Shadow @Final private PlayerChunkWatchingManager playerChunkWatchingManager; 30 | 31 | @Shadow private int watchDistance; 32 | 33 | @Shadow @Final private static Logger LOGGER; 34 | 35 | @Shadow @Final private ServerChunkLoadingManager.TicketManager ticketManager; 36 | 37 | @Shadow protected abstract boolean canTickChunk(ServerPlayerEntity player, ChunkPos pos); 38 | 39 | @Shadow protected abstract void track(ServerPlayerEntity player, ChunkPos pos); 40 | 41 | @Shadow 42 | protected static void untrack(ServerPlayerEntity player, ChunkPos pos) { 43 | throw new AbstractMethodError(); 44 | } 45 | 46 | @Shadow protected abstract void updateWatchedSection(ServerPlayerEntity player); 47 | 48 | @Shadow abstract int getViewDistance(ServerPlayerEntity player); 49 | 50 | @Unique 51 | private AreaPlayerChunkWatchingManager areaPlayerChunkWatchingManager; 52 | 53 | @Override 54 | public AreaPlayerChunkWatchingManager getAreaPlayerChunkWatchingManager() { 55 | return this.areaPlayerChunkWatchingManager; 56 | } 57 | 58 | @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkLoadingManager;setViewDistance(I)V")) 59 | private void redirectNewPlayerChunkWatchingManager(CallbackInfo ci) { 60 | this.areaPlayerChunkWatchingManager = new AreaPlayerChunkWatchingManager( 61 | (player, chunkX, chunkZ) -> this.track(player, new ChunkPos(chunkX, chunkZ)), 62 | (player, chunkX, chunkZ) -> untrack(player, new ChunkPos(chunkX, chunkZ)), 63 | (ServerChunkLoadingManager) (Object) this); 64 | } 65 | 66 | @Inject(method = "tick", at = @At("RETURN")) 67 | private void onTick(CallbackInfo ci) { 68 | areaPlayerChunkWatchingManager.tick(); 69 | } 70 | 71 | @Inject(method = "setViewDistance", at = @At("RETURN")) 72 | private void onSetViewDistance(CallbackInfo ci) { 73 | if (Config.SHOW_CHUNK_TRACKING_MESSAGES) { 74 | LOGGER.info("Changing watch distance to {}", this.watchDistance); 75 | } 76 | areaPlayerChunkWatchingManager.setWatchDistance(this.watchDistance); 77 | } 78 | 79 | /** 80 | * @author ishland 81 | * @reason use array for iteration & use squares as cylinders are expensive 82 | */ 83 | @Overwrite 84 | public List getPlayersWatchingChunk(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) { 85 | final AreaPlayerChunkWatchingManager watchingManager = this.areaPlayerChunkWatchingManager; 86 | 87 | // if (!onlyOnWatchDistanceEdge) { 88 | // // implementation details: this object implements java.util.List 89 | // // but does not support any of the java.util.List specific methods 90 | // // this may cause incompatibility for mods that expects a good java.util.List 91 | // return (List) watchingManager.getPlayersWatchingChunk(chunkPos.toLong()); 92 | // } 93 | 94 | Object[] set = watchingManager.getPlayersWatchingChunkArray(chunkPos.toLong()); 95 | ImmutableList.Builder builder = ImmutableList.builder(); 96 | 97 | for (Object __player : set) { 98 | if (__player instanceof ServerPlayerEntity serverPlayerEntity) { 99 | ChunkSectionPos watchedPos = serverPlayerEntity.getWatchedSection(); 100 | int chebyshevDistance = Math.max(Math.abs(watchedPos.getSectionX() - chunkPos.x), Math.abs(watchedPos.getSectionZ() - chunkPos.z)); 101 | if (chebyshevDistance > this.watchDistance) { 102 | continue; 103 | } 104 | if (!serverPlayerEntity.networkHandler.chunkDataSender.isInNextBatch(chunkPos.toLong()) && 105 | (!onlyOnWatchDistanceEdge || chebyshevDistance == this.watchDistance)) { 106 | builder.add(serverPlayerEntity); 107 | } 108 | } 109 | } 110 | 111 | return builder.build(); 112 | } 113 | 114 | /** 115 | * @author ishland 116 | * @reason use array for iteration 117 | */ 118 | @Overwrite 119 | public List getPlayersWatchingChunk(ChunkPos pos) { 120 | long l = pos.toLong(); 121 | if (!this.ticketManager.shouldTick(l)) { 122 | return List.of(); 123 | } else { 124 | ImmutableList.Builder builder = ImmutableList.builder(); 125 | 126 | for (Object __player : this.areaPlayerChunkWatchingManager.getPlayersInGeneralAreaMap(l)) { 127 | if (__player instanceof ServerPlayerEntity serverPlayerEntity) { 128 | if (this.canTickChunk(serverPlayerEntity, pos)) { 129 | builder.add(serverPlayerEntity); 130 | } 131 | } 132 | } 133 | 134 | return builder.build(); 135 | } 136 | } 137 | 138 | /** 139 | * @author ishland 140 | * @reason use array for iteration 141 | */ 142 | @Overwrite 143 | public boolean shouldTick(ChunkPos pos) { 144 | long l = pos.toLong(); 145 | if (!this.ticketManager.shouldTick(l)) { 146 | return false; 147 | } else { 148 | for (Object __player : this.areaPlayerChunkWatchingManager.getPlayersInGeneralAreaMap(l)) { 149 | if (__player instanceof ServerPlayerEntity serverPlayerEntity) { 150 | if (this.canTickChunk(serverPlayerEntity, pos)) { 151 | return true; 152 | } 153 | } 154 | } 155 | return false; 156 | } 157 | } 158 | 159 | @Inject(method = "handlePlayerAddedOrRemoved", at = @At("HEAD")) 160 | private void onHandlePlayerAddedOrRemoved(ServerPlayerEntity player, boolean added, CallbackInfo ci) { 161 | if (added) { 162 | this.vmp$updateWatchedSection(player); 163 | this.areaPlayerChunkWatchingManager.add(player, player.getWatchedSection().toChunkPos().toLong()); 164 | } else { 165 | this.areaPlayerChunkWatchingManager.remove(player); 166 | } 167 | } 168 | 169 | @Inject(method = "updatePosition", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkLoadingManager;updateWatchedSection(Lnet/minecraft/server/network/ServerPlayerEntity;)V")) 170 | private void onPlayerSectionChange(ServerPlayerEntity player, CallbackInfo ci) { 171 | this.vmp$updateWatchedSection(player); 172 | this.areaPlayerChunkWatchingManager.movePlayer(player.getWatchedSection().toChunkPos().toLong(), player); 173 | } 174 | 175 | @Unique 176 | private void vmp$updateWatchedSection(ServerPlayerEntity player) { 177 | this.updateWatchedSection(player); 178 | player.networkHandler.sendPacket(new ChunkRenderDistanceCenterS2CPacket(player.getWatchedSection().getSectionX(), player.getWatchedSection().getSectionZ())); 179 | player.setChunkFilter(ChunkFilter.cylindrical(player.getWatchedSection().toChunkPos(), this.getViewDistance(player))); 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/optimize_nearby_entity_tracking_lookups/MixinEntityTrackerEntry.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching.optimize_nearby_entity_tracking_lookups; 2 | 3 | import com.ishland.vmp.common.playerwatching.EntityTrackerEntryExtension; 4 | import net.minecraft.entity.Entity; 5 | import net.minecraft.server.network.EntityTrackerEntry; 6 | import net.minecraft.util.math.MathHelper; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | 11 | @Mixin(EntityTrackerEntry.class) 12 | public abstract class MixinEntityTrackerEntry implements EntityTrackerEntryExtension { 13 | 14 | 15 | @Shadow private int trackingTick; 16 | 17 | @Shadow @Final private int tickInterval; 18 | 19 | @Shadow public abstract void tick(); 20 | 21 | @Shadow protected abstract void syncEntityData(); 22 | 23 | @Shadow @Final private Entity entity; 24 | 25 | @Shadow private int updatesWithoutVehicle; 26 | 27 | @Override 28 | public void vmp$tickAlways() { 29 | this.trackingTick = MathHelper.roundUpToMultiple(this.trackingTick, this.tickInterval); 30 | this.updatesWithoutVehicle = 1 << 16; 31 | this.entity.velocityDirty = true; 32 | this.tick(); 33 | } 34 | 35 | @Override 36 | public void vmp$syncEntityData() { 37 | trackingTick ++; 38 | if (this.trackingTick % this.tickInterval == 0) { // [VanillaCopy] 39 | this.syncEntityData(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/optimize_nearby_entity_tracking_lookups/MixinServerPlayerEntity.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching.optimize_nearby_entity_tracking_lookups; 2 | 3 | import com.ishland.vmp.common.playerwatching.ServerPlayerEntityExtension; 4 | import com.mojang.authlib.GameProfile; 5 | import net.minecraft.entity.player.PlayerEntity; 6 | import net.minecraft.network.encryption.PlayerPublicKey; 7 | import net.minecraft.server.network.ServerPlayerEntity; 8 | import net.minecraft.util.math.BlockPos; 9 | import net.minecraft.util.math.Vec3d; 10 | import net.minecraft.world.World; 11 | import org.jetbrains.annotations.Nullable; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | 14 | @Mixin(ServerPlayerEntity.class) 15 | public abstract class MixinServerPlayerEntity extends PlayerEntity implements ServerPlayerEntityExtension { 16 | 17 | public MixinServerPlayerEntity(World world, BlockPos pos, float yaw, GameProfile gameProfile) { 18 | super(world, pos, yaw, gameProfile); 19 | } 20 | 21 | private double vmpTracking$prevX = Double.NaN; 22 | private double vmpTracking$prevY = Double.NaN; 23 | private double vmpTracking$prevZ = Double.NaN; 24 | 25 | @Override 26 | public boolean vmpTracking$isPositionUpdated() { 27 | final Vec3d pos = this.getPos(); 28 | return pos.x != this.vmpTracking$prevX || pos.y != this.vmpTracking$prevY || pos.z != this.vmpTracking$prevZ; 29 | } 30 | 31 | @Override 32 | public void vmpTracking$updatePosition() { 33 | final Vec3d pos = this.getPos(); 34 | this.vmpTracking$prevX = pos.x; 35 | this.vmpTracking$prevY = pos.y; 36 | this.vmpTracking$prevZ = pos.z; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/optimize_nearby_entity_tracking_lookups/MixinThreadedAnvilChunkStorage.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching.optimize_nearby_entity_tracking_lookups; 2 | 3 | import com.ishland.vmp.common.playerwatching.NearbyEntityTracking; 4 | import com.ishland.vmp.mixins.access.IThreadedAnvilChunkStorageEntityTracker; 5 | import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 6 | import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; 7 | import it.unimi.dsi.fastutil.objects.ObjectCollection; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import net.minecraft.server.world.PlayerChunkWatchingManager; 10 | import net.minecraft.server.world.ServerChunkLoadingManager; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Overwrite; 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.Redirect; 18 | 19 | import java.util.List; 20 | 21 | @Mixin(ServerChunkLoadingManager.class) 22 | public class MixinThreadedAnvilChunkStorage { 23 | 24 | @Shadow 25 | @Final 26 | private Int2ObjectMap entityTrackers; 27 | @Shadow @Final private ServerChunkLoadingManager.TicketManager ticketManager; 28 | @Shadow @Final private PlayerChunkWatchingManager playerChunkWatchingManager; 29 | @Unique 30 | private final NearbyEntityTracking nearbyEntityTracking = new NearbyEntityTracking(); 31 | 32 | 33 | @Redirect(method = "loadEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkLoadingManager$EntityTracker;updateTrackedStatus(Ljava/util/List;)V")) 34 | private void redirectUpdateOnAddEntity(ServerChunkLoadingManager.EntityTracker instance, List players) { 35 | this.nearbyEntityTracking.addEntityTracker(instance); 36 | } 37 | 38 | @Redirect(method = "loadEntity", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;values()Lit/unimi/dsi/fastutil/objects/ObjectCollection;")) 39 | private ObjectCollection nullifyTrackerListOnAddEntity(Int2ObjectMap instance) { 40 | if (this.entityTrackers == instance) return Int2ObjectMaps.emptyMap().values(); 41 | else return instance.values(); 42 | } 43 | 44 | @Redirect(method = "unloadEntity", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;values()Lit/unimi/dsi/fastutil/objects/ObjectCollection;")) 45 | private ObjectCollection nullifyTrackerListOnRemoveEntity(Int2ObjectMap instance) { 46 | if (this.entityTrackers == instance) return Int2ObjectMaps.emptyMap().values(); 47 | else return instance.values(); 48 | } 49 | 50 | @Redirect(method = "unloadEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkLoadingManager$EntityTracker;stopTracking()V")) 51 | private void redirectUpdateOnRemoveEntity(ServerChunkLoadingManager.EntityTracker instance) { 52 | this.nearbyEntityTracking.removeEntityTracker(instance); 53 | instance.stopTracking(); 54 | } 55 | 56 | @Redirect(method = "updatePosition", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;values()Lit/unimi/dsi/fastutil/objects/ObjectCollection;")) 57 | private ObjectCollection redirectTrackersOnUpdatePosition(Int2ObjectMap instance, ServerPlayerEntity player) { 58 | if (this.entityTrackers != instance) { 59 | return instance.values(); 60 | } else { 61 | return Int2ObjectMaps.emptyMap().values(); // nullify, already handled in tick call 62 | } 63 | } 64 | 65 | /** 66 | * @author ishland 67 | * @reason use nearby tracker lookup 68 | */ 69 | @Overwrite 70 | public void tickEntityMovement() { 71 | // for(ServerPlayerEntity serverPlayerEntity : this.playerChunkWatchingManager.getPlayersWatchingChunk()) { 72 | //// this.sendWatchPackets(serverPlayerEntity); // done with distance maps 73 | // serverPlayerEntity.networkHandler.chunkDataSender.sendChunkBatches(serverPlayerEntity); 74 | // } 75 | 76 | try { 77 | this.nearbyEntityTracking.tick(this.ticketManager); 78 | } catch (Throwable t) { 79 | t.printStackTrace(); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/optimize_nearby_entity_tracking_lookups/MixinThreadedAnvilChunkStorageEntityTracker.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching.optimize_nearby_entity_tracking_lookups; 2 | 3 | import com.ishland.vmp.common.playerwatching.EntityTrackerEntryExtension; 4 | import com.ishland.vmp.common.playerwatching.EntityTrackerExtension; 5 | import com.ishland.vmp.mixins.access.IEntityTrackerEntry; 6 | import net.minecraft.entity.Entity; 7 | import net.minecraft.network.packet.s2c.play.EntityVelocityUpdateS2CPacket; 8 | import net.minecraft.server.network.EntityTrackerEntry; 9 | import net.minecraft.server.network.PlayerAssociatedNetworkHandler; 10 | import net.minecraft.server.network.ServerPlayerEntity; 11 | import net.minecraft.server.world.ServerChunkLoadingManager; 12 | import net.minecraft.util.math.ChunkPos; 13 | import net.minecraft.util.math.ChunkSectionPos; 14 | import net.minecraft.util.math.Vec3d; 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.Unique; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.Redirect; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 23 | 24 | import java.util.List; 25 | import java.util.Set; 26 | 27 | @Mixin(ServerChunkLoadingManager.EntityTracker.class) 28 | public abstract class MixinThreadedAnvilChunkStorageEntityTracker implements EntityTrackerExtension { 29 | 30 | @Shadow @Final private Entity entity; 31 | @Shadow @Final private Set listeners; 32 | 33 | @Shadow public abstract void updateTrackedStatus(ServerPlayerEntity player); 34 | 35 | @Shadow @Final private EntityTrackerEntry entry; 36 | @Shadow private ChunkSectionPos trackedSection; 37 | @Unique 38 | private double prevX = Double.NaN; 39 | 40 | @Unique 41 | private double prevY = Double.NaN; 42 | 43 | @Unique 44 | private double prevZ = Double.NaN; 45 | 46 | @Override 47 | public boolean isPositionUpdated() { 48 | final Vec3d pos = this.entity.getPos(); 49 | return pos.x != this.prevX || pos.y != this.prevY || pos.z != prevZ; 50 | } 51 | 52 | @Override 53 | public void updatePosition() { 54 | final Vec3d pos = this.entity.getPos(); 55 | this.prevX = pos.x; 56 | this.prevY = pos.y; 57 | this.prevZ = pos.z; 58 | this.trackedSection = ChunkSectionPos.from(this.entity); 59 | } 60 | 61 | @Override 62 | public Vec3d getPreviousLocation() { 63 | return new Vec3d(this.prevX, this.prevY, this.prevZ); 64 | } 65 | 66 | @Override 67 | public long getPreviousChunkPos() { 68 | return ChunkPos.toLong(ChunkSectionPos.getSectionCoord((int) this.prevX), ChunkSectionPos.getSectionCoord((int) this.prevX)); 69 | } 70 | 71 | @Override 72 | public void updateListeners(Set triedPlayers) { 73 | for (PlayerAssociatedNetworkHandler listener : this.listeners.toArray(PlayerAssociatedNetworkHandler[]::new)) { 74 | final ServerPlayerEntity player = listener.getPlayer(); 75 | if (triedPlayers != null) triedPlayers.add(player); 76 | if (player != null) this.updateTrackedStatus(player); 77 | } 78 | } 79 | 80 | @Override 81 | public void tryTick() { 82 | this.trackedSection = ChunkSectionPos.from(this.entity); 83 | this.entry.tick(); 84 | } 85 | 86 | @Inject(method = "updateTrackedStatus(Lnet/minecraft/server/network/ServerPlayerEntity;)V", at = @At(value = "INVOKE", target = "Ljava/util/Set;add(Ljava/lang/Object;)Z", shift = At.Shift.BEFORE)) 87 | private void beforeStartTracking(ServerPlayerEntity player, CallbackInfo ci) { 88 | if (this.listeners.isEmpty()) { 89 | ((EntityTrackerEntryExtension) this.entry).vmp$tickAlways(); 90 | } 91 | } 92 | 93 | @Redirect(method = "updateTrackedStatus(Lnet/minecraft/server/network/ServerPlayerEntity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkLoadingManager;isTracked(Lnet/minecraft/server/network/ServerPlayerEntity;II)Z")) 94 | private boolean assumeAlwaysTracked(ServerChunkLoadingManager instance, ServerPlayerEntity player, int chunkX, int chunkZ) { 95 | return true; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/optimize_nearby_player_lookups/MixinMobEntity.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching.optimize_nearby_player_lookups; 2 | 3 | import net.minecraft.entity.Entity; 4 | import net.minecraft.entity.EntityType; 5 | import net.minecraft.entity.LivingEntity; 6 | import net.minecraft.entity.mob.MobEntity; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.predicate.entity.EntityPredicates; 9 | import net.minecraft.world.World; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Redirect; 13 | 14 | import java.util.List; 15 | 16 | @Mixin(MobEntity.class) 17 | public abstract class MixinMobEntity extends LivingEntity { 18 | 19 | protected MixinMobEntity(EntityType entityType, World world) { 20 | super(entityType, world); 21 | } 22 | 23 | @Redirect(method = "checkDespawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;getClosestPlayer(Lnet/minecraft/entity/Entity;D)Lnet/minecraft/entity/player/PlayerEntity;")) 24 | private PlayerEntity redirectGetClosestPlayer(World instance, Entity entity, double maxDistance) { 25 | final PlayerEntity closestPlayer = instance.getClosestPlayer(entity, this.getType().getSpawnGroup().getImmediateDespawnRange()); 26 | if (closestPlayer != null) { 27 | return closestPlayer; 28 | } else { 29 | for (PlayerEntity player : this.getWorld().getPlayers()) { 30 | if (EntityPredicates.EXCEPT_SPECTATOR.test(player)) { 31 | return player; 32 | } 33 | } 34 | return null; 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/optimize_nearby_player_lookups/MixinServerWorld.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching.optimize_nearby_player_lookups; 2 | 3 | import com.ishland.vmp.common.chunkwatching.AreaPlayerChunkWatchingManager; 4 | import com.ishland.vmp.common.playerwatching.TACSExtension; 5 | import io.papermc.paper.util.MCUtil; 6 | import net.minecraft.entity.Entity; 7 | import net.minecraft.entity.LivingEntity; 8 | import net.minecraft.entity.ai.TargetPredicate; 9 | import net.minecraft.entity.player.PlayerEntity; 10 | import net.minecraft.registry.DynamicRegistryManager; 11 | import net.minecraft.registry.RegistryKey; 12 | import net.minecraft.registry.entry.RegistryEntry; 13 | import net.minecraft.server.network.ServerPlayerEntity; 14 | import net.minecraft.server.world.ServerChunkLoadingManager; 15 | import net.minecraft.server.world.ServerChunkManager; 16 | import net.minecraft.server.world.ServerWorld; 17 | import net.minecraft.util.math.ChunkSectionPos; 18 | import net.minecraft.world.MutableWorldProperties; 19 | import net.minecraft.world.StructureWorldAccess; 20 | import net.minecraft.world.World; 21 | import net.minecraft.world.dimension.DimensionType; 22 | import org.jetbrains.annotations.Nullable; 23 | import org.spongepowered.asm.mixin.Mixin; 24 | import org.spongepowered.asm.mixin.Shadow; 25 | 26 | import java.util.function.Predicate; 27 | 28 | @Mixin(ServerWorld.class) 29 | public abstract class MixinServerWorld extends World implements StructureWorldAccess { 30 | 31 | protected MixinServerWorld(MutableWorldProperties properties, RegistryKey registryRef, DynamicRegistryManager registryManager, RegistryEntry dimensionEntry, boolean isClient, boolean debugWorld, long seed, int maxChainedNeighborUpdates) { 32 | super(properties, registryRef, registryManager, dimensionEntry, isClient, debugWorld, seed, maxChainedNeighborUpdates); 33 | } 34 | 35 | @Shadow public abstract ServerChunkManager getChunkManager(); 36 | 37 | @Nullable 38 | @Override 39 | public PlayerEntity getClosestPlayer(double x, double y, double z, double maxDistance, @Nullable Predicate targetPredicate) { 40 | final ServerChunkLoadingManager threadedAnvilChunkStorage = this.getChunkManager().chunkLoadingManager; 41 | final AreaPlayerChunkWatchingManager playerChunkWatchingManager = ((TACSExtension) threadedAnvilChunkStorage).getAreaPlayerChunkWatchingManager(); 42 | final int chunkX = ChunkSectionPos.getSectionCoord(x); 43 | final int chunkZ = ChunkSectionPos.getSectionCoord(z); 44 | 45 | if (AreaPlayerChunkWatchingManager.GENERAL_PLAYER_AREA_MAP_DISTANCE * 16 < maxDistance || maxDistance < 0.0D) // too far away for this to handle 46 | return super.getClosestPlayer(x, y, z, maxDistance, targetPredicate); 47 | 48 | final Object[] playersWatchingChunkArray = playerChunkWatchingManager.getPlayersInGeneralAreaMap(MCUtil.getCoordinateKey(chunkX, chunkZ)); 49 | 50 | ServerPlayerEntity nearestPlayer = null; 51 | double nearestDistance = maxDistance * maxDistance; // maxDistance < 0.0D handled above 52 | for (Object __player : playersWatchingChunkArray) { 53 | if (__player instanceof ServerPlayerEntity player) { 54 | if (targetPredicate == null || targetPredicate.test(player)) { 55 | final double distance = player.squaredDistanceTo(x, y, z); 56 | if (distance < nearestDistance) { 57 | nearestDistance = distance; 58 | nearestPlayer = player; 59 | } 60 | } 61 | } 62 | } 63 | 64 | return nearestPlayer; 65 | } 66 | 67 | @Override 68 | public boolean isPlayerInRange(double x, double y, double z, double range) { 69 | final ServerChunkLoadingManager threadedAnvilChunkStorage = this.getChunkManager().chunkLoadingManager; 70 | final AreaPlayerChunkWatchingManager playerChunkWatchingManager = ((TACSExtension) threadedAnvilChunkStorage).getAreaPlayerChunkWatchingManager(); 71 | final int chunkX = ChunkSectionPos.getSectionCoord(x); 72 | final int chunkZ = ChunkSectionPos.getSectionCoord(z); 73 | 74 | if (AreaPlayerChunkWatchingManager.GENERAL_PLAYER_AREA_MAP_DISTANCE * 16 < range) // too far away for this to handle 75 | return super.isPlayerInRange(x, y, z, range); 76 | 77 | final Object[] playersWatchingChunkArray = playerChunkWatchingManager.getPlayersWatchingChunkArray(MCUtil.getCoordinateKey(chunkX, chunkZ)); 78 | 79 | double rangeSquared = range * range; 80 | 81 | for (Object __player : playersWatchingChunkArray) { 82 | if (__player instanceof ServerPlayerEntity player) { 83 | if (!player.isSpectator() && player.isAlive()) { 84 | final double distance = player.squaredDistanceTo(x, y, z); 85 | if (range < 0.0 || distance < rangeSquared) { 86 | return true; 87 | } 88 | } 89 | } 90 | } 91 | return false; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/optimize_nearby_player_lookups/MixinSpawnDensityCapper.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching.optimize_nearby_player_lookups; 2 | 3 | import com.ishland.vmp.common.chunkwatching.AreaPlayerChunkWatchingManager; 4 | import com.ishland.vmp.common.playerwatching.TACSExtension; 5 | import com.ishland.vmp.mixins.access.IThreadedAnvilChunkStorage; 6 | import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; 7 | import net.minecraft.entity.SpawnGroup; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import net.minecraft.server.world.ServerChunkLoadingManager; 10 | import net.minecraft.util.math.ChunkPos; 11 | import net.minecraft.world.SpawnDensityCapper; 12 | import org.spongepowered.asm.mixin.Final; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Mutable; 15 | import org.spongepowered.asm.mixin.Overwrite; 16 | import org.spongepowered.asm.mixin.Shadow; 17 | import org.spongepowered.asm.mixin.Unique; 18 | import org.spongepowered.asm.mixin.injection.At; 19 | import org.spongepowered.asm.mixin.injection.Inject; 20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 21 | 22 | import java.util.Map; 23 | import java.util.function.Function; 24 | 25 | @Mixin(value = SpawnDensityCapper.class, priority = 950) 26 | public abstract class MixinSpawnDensityCapper { 27 | 28 | @Shadow 29 | @Final 30 | private ServerChunkLoadingManager chunkLoadingManager; 31 | 32 | @Mutable 33 | @Shadow @Final private Map playersToDensityCap; 34 | private static final Function newDensityCap = ignored -> new SpawnDensityCapper.DensityCap(); 35 | 36 | @Inject(method = "", at = @At("RETURN")) 37 | private void onInit(CallbackInfo info) { 38 | this.playersToDensityCap = new Reference2ReferenceOpenHashMap<>(); 39 | } 40 | 41 | @Unique 42 | private Object[] getMobSpawnablePlayersArray(ChunkPos chunkPos) { 43 | final AreaPlayerChunkWatchingManager manager = ((TACSExtension) this.chunkLoadingManager).getAreaPlayerChunkWatchingManager(); 44 | return manager.getPlayersInGeneralAreaMap(chunkPos.toLong()); 45 | } 46 | 47 | private static double sqrDistance(double x1, double y1, double x2, double y2) { 48 | final double dx = x1 - x2; 49 | final double dy = y1 - y2; 50 | return dx * dx + dy * dy; 51 | } 52 | 53 | /** 54 | * @author ishland 55 | * @reason optimize & reduce allocations 56 | */ 57 | @Overwrite 58 | public void increaseDensity(ChunkPos chunkPos, SpawnGroup spawnGroup) { 59 | final double centerX = chunkPos.getCenterX(); 60 | final double centerZ = chunkPos.getCenterZ(); 61 | for(Object _player : this.getMobSpawnablePlayersArray(chunkPos)) { 62 | if (_player instanceof ServerPlayerEntity player && !player.isSpectator() && sqrDistance(centerX, centerZ, player.getX(), player.getZ()) <= 16384.0D) { 63 | this.playersToDensityCap.computeIfAbsent(player, newDensityCap).increaseDensity(spawnGroup); 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * @author ishland 70 | * @reason optimize & reduce allocations 71 | */ 72 | @Overwrite 73 | public boolean canSpawn(SpawnGroup spawnGroup, ChunkPos chunkPos) { 74 | final double centerX = chunkPos.getCenterX(); 75 | final double centerZ = chunkPos.getCenterZ(); 76 | for(Object _player : this.getMobSpawnablePlayersArray(chunkPos)) { 77 | if (_player instanceof ServerPlayerEntity player && !player.isSpectator() && sqrDistance(centerX, centerZ, player.getX(), player.getZ()) <= 16384.0D) { 78 | SpawnDensityCapper.DensityCap densityCap = this.playersToDensityCap.get(player); 79 | if (densityCap == null || densityCap.canSpawn(spawnGroup)) { 80 | return true; 81 | } 82 | } 83 | } 84 | 85 | return false; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/playerwatching/optimize_nearby_player_lookups/MixinThreadedAnvilChunkStorage.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.playerwatching.optimize_nearby_player_lookups; 2 | 3 | import com.ishland.vmp.common.chunkwatching.AreaPlayerChunkWatchingManager; 4 | import net.minecraft.entity.Entity; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | import net.minecraft.server.world.PlayerChunkWatchingManager; 7 | import net.minecraft.server.world.ServerChunkLoadingManager; 8 | import net.minecraft.util.math.ChunkPos; 9 | import org.spongepowered.asm.mixin.Final; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Overwrite; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | 14 | @Mixin(ServerChunkLoadingManager.class) 15 | public abstract class MixinThreadedAnvilChunkStorage { 16 | 17 | @Shadow @Final private PlayerChunkWatchingManager playerChunkWatchingManager; 18 | 19 | @Shadow @Final private ServerChunkLoadingManager.TicketManager ticketManager; 20 | 21 | @Shadow 22 | private static double getSquaredDistance(ChunkPos pos, Entity entity) { 23 | throw new AbstractMethodError(); 24 | } 25 | 26 | // /** 27 | // * @author ishland 28 | // * @reason optimize nearby player lookups 29 | // */ 30 | // @Overwrite 31 | // public boolean isTooFarFromPlayersToSpawnMobs(ChunkPos chunkPos) { 32 | // long l = chunkPos.toLong(); 33 | // if (!this.ticketManager.shouldTick(l)) return true; 34 | // final AreaPlayerChunkWatchingManager chunkWatchingManager = (AreaPlayerChunkWatchingManager) this.playerChunkWatchingManager; 35 | // final Object[] array = chunkWatchingManager.getPlayersWatchingChunkArray(chunkPos.toLong()); 36 | // 37 | // for (Object __player : array) { 38 | // if (__player instanceof ServerPlayerEntity player) { 39 | // if (!player.isSpectator() && getSquaredDistance(chunkPos, player) < 16384.0) return false; 40 | // } 41 | // } 42 | // return true; 43 | // } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/ticketsystem/package-info.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.ticketsystem; -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/ticketsystem/ticketpropagator/MixinChunkTicketManager.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.ticketsystem.ticketpropagator; 2 | 3 | import com.ishland.vmp.mixins.access.IAbstractChunkHolder; 4 | import com.ishland.vmp.mixins.access.IChunkHolder; 5 | import com.ishland.vmp.mixins.access.IThreadedAnvilChunkStorage; 6 | import io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D; 7 | import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; 8 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; 9 | import it.unimi.dsi.fastutil.longs.LongSet; 10 | import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; 11 | import net.minecraft.server.world.ChunkHolder; 12 | import net.minecraft.server.world.ChunkLevels; 13 | import net.minecraft.server.world.ChunkTicket; 14 | import net.minecraft.server.world.ChunkTicketManager; 15 | import net.minecraft.server.world.ServerChunkLoadingManager; 16 | import net.minecraft.util.collection.SortedArraySet; 17 | import org.jetbrains.annotations.Nullable; 18 | import org.spongepowered.asm.mixin.Final; 19 | import org.spongepowered.asm.mixin.Mixin; 20 | import org.spongepowered.asm.mixin.Mutable; 21 | import org.spongepowered.asm.mixin.Shadow; 22 | import org.spongepowered.asm.mixin.Unique; 23 | import org.spongepowered.asm.mixin.injection.At; 24 | import org.spongepowered.asm.mixin.injection.Inject; 25 | import org.spongepowered.asm.mixin.injection.Redirect; 26 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 27 | 28 | import java.util.ArrayList; 29 | import java.util.ConcurrentModificationException; 30 | import java.util.Set; 31 | import java.util.concurrent.Executor; 32 | 33 | @Mixin(ChunkTicketManager.class) 34 | public abstract class MixinChunkTicketManager { 35 | 36 | @Mutable 37 | @Shadow @Final private ChunkTicketManager.TicketDistanceLevelPropagator distanceFromTicketTracker; 38 | 39 | @Shadow protected @Nullable abstract ChunkHolder getChunkHolder(long pos); 40 | 41 | @Shadow protected @Nullable abstract ChunkHolder setLevel(long pos, int level, @Nullable ChunkHolder holder, int i); 42 | 43 | @Shadow @Final private ChunkTicketManager.NearbyChunkTicketUpdater nearbyChunkTicketUpdater; 44 | @Shadow @Final private Set chunkHoldersWithPendingUpdates; 45 | @Shadow @Final private Executor mainThreadExecutor; 46 | @Shadow @Final private LongSet freshPlayerTicketPositions; 47 | 48 | @Shadow protected abstract SortedArraySet> getTicketSet(long position); 49 | 50 | @Shadow 51 | protected static int getLevel(SortedArraySet> sortedArraySet) { 52 | throw new AbstractMethodError(); 53 | } 54 | 55 | @Unique 56 | protected Long2IntLinkedOpenHashMap ticketLevelUpdates; 57 | 58 | @Unique 59 | protected io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ticketLevelPropagator; 60 | 61 | @Unique 62 | private ObjectArrayFIFOQueue pendingChunkHolderUpdates; 63 | 64 | // Paper distance map propagates level from max to 0 while vanilla 65 | // one propagate from 0 to max 66 | // So there need a conversion between these values 67 | 68 | @Unique 69 | private static int convertBetweenTicketLevels(final int level) { 70 | return ChunkLevels.INACCESSIBLE - level + 1; 71 | } 72 | 73 | @Unique 74 | protected final void updateTicketLevel(final long coordinate, final int ticketLevel) { 75 | if (ticketLevel > ChunkLevels.INACCESSIBLE) { 76 | this.ticketLevelPropagator.removeSource(coordinate); 77 | } else { 78 | this.ticketLevelPropagator.setSource(coordinate, convertBetweenTicketLevels(ticketLevel)); 79 | } 80 | } 81 | 82 | @Inject(method = "", at = @At("RETURN")) 83 | private void onInit(Executor workerExecutor, Executor mainThreadExecutor, CallbackInfo ci) { 84 | this.distanceFromTicketTracker = null; // fail-fast incompatibility 85 | 86 | this.ticketLevelUpdates = new Long2IntLinkedOpenHashMap() { 87 | @Override 88 | protected void rehash(int newN) { 89 | if (newN < this.n) { 90 | return; 91 | } 92 | super.rehash(newN); 93 | } 94 | }; 95 | this.ticketLevelPropagator = new Delayed8WayDistancePropagator2D( 96 | (long coordinate, byte oldLevel, byte newLevel) -> { 97 | this.ticketLevelUpdates.putAndMoveToLast(coordinate, convertBetweenTicketLevels(newLevel)); 98 | } 99 | ); 100 | this.pendingChunkHolderUpdates = new ObjectArrayFIFOQueue<>(); 101 | } 102 | 103 | @Redirect(method = {"purgeExpiredTickets", "addTicket(JLnet/minecraft/server/world/ChunkTicket;)V", "removeTicket(JLnet/minecraft/server/world/ChunkTicket;)V", "removePersistentTickets"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ChunkTicketManager$TicketDistanceLevelPropagator;updateLevel(JIZ)V"), require = 4, expect = 4) 104 | private void redirectUpdate(ChunkTicketManager.TicketDistanceLevelPropagator instance, long l, int i, boolean b) { 105 | this.updateTicketLevel(l, i); 106 | } 107 | 108 | @Redirect(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ChunkTicketManager$TicketDistanceLevelPropagator;update(I)I")) 109 | public int tickTickets(ChunkTicketManager.TicketDistanceLevelPropagator __, int distance, ServerChunkLoadingManager threadedAnvilChunkStorage) { 110 | if (!((IThreadedAnvilChunkStorage) threadedAnvilChunkStorage).getMainThreadExecutor().isOnThread()) { 111 | throw new ConcurrentModificationException("Attempted to tick tickets asynchronously"); 112 | } 113 | 114 | boolean hasUpdates = this.ticketLevelPropagator.propagateUpdates(); 115 | if (hasUpdates) { 116 | } 117 | 118 | while (!this.ticketLevelUpdates.isEmpty()) { 119 | hasUpdates = true; 120 | 121 | long key = this.ticketLevelUpdates.firstLongKey(); 122 | int newLevel = this.ticketLevelUpdates.removeFirstInt(); 123 | 124 | ChunkHolder holder = this.getChunkHolder(key); 125 | int currentLevel = holder == null ? ChunkLevels.INACCESSIBLE + 1 : holder.getLevel(); 126 | if (newLevel == currentLevel) continue; 127 | 128 | holder = this.setLevel(key, newLevel, holder, currentLevel); 129 | 130 | if (holder == null) { 131 | if (newLevel <= ChunkLevels.INACCESSIBLE) { 132 | throw new IllegalStateException("Chunk holder not created"); 133 | } 134 | continue; 135 | } 136 | 137 | this.pendingChunkHolderUpdates.enqueue(holder); 138 | } 139 | 140 | ArrayList pending = new ArrayList<>(this.pendingChunkHolderUpdates.size()); 141 | while (!this.pendingChunkHolderUpdates.isEmpty()) { 142 | pending.add(this.pendingChunkHolderUpdates.dequeue()); 143 | } 144 | this.pendingChunkHolderUpdates.clear(); 145 | for (ChunkHolder element : pending) { 146 | ((IAbstractChunkHolder) element).invokeUpdateStatus(threadedAnvilChunkStorage); 147 | } 148 | for (ChunkHolder element : pending) { 149 | ((IChunkHolder) element).invokeUpdateFutures(threadedAnvilChunkStorage, this.mainThreadExecutor); 150 | } 151 | 152 | return hasUpdates ? distance - 1 : distance; 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/com/ishland/vmp/mixins/timesource/MixinMinecraftClient.java: -------------------------------------------------------------------------------- 1 | package com.ishland.vmp.mixins.timesource; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.util.Util; 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 | @Environment(EnvType.CLIENT) 13 | @Mixin(MinecraftClient.class) 14 | public class MixinMinecraftClient { 15 | 16 | @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;onWindowFocusChanged(Z)V")) 17 | private void afterTimeSourceChange(CallbackInfo ci) { 18 | Util.nanoTimeSupplier = System::nanoTime; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/papermc/paper/util/IntegerUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under https://github.com/PaperMC/Paper/blob/master/licenses/MIT.md 3 | */ 4 | 5 | package io.papermc.paper.util; 6 | 7 | public final class IntegerUtil { 8 | 9 | public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; 10 | public static final long HIGH_BIT_U64 = Long.MIN_VALUE; 11 | 12 | public static int ceilLog2(final int value) { 13 | return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros 14 | } 15 | 16 | public static long ceilLog2(final long value) { 17 | return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros 18 | } 19 | 20 | public static int floorLog2(final int value) { 21 | // xor is optimized subtract for 2^n -1 22 | // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) 23 | return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros 24 | } 25 | 26 | public static int floorLog2(final long value) { 27 | // xor is optimized subtract for 2^n -1 28 | // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) 29 | return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros 30 | } 31 | 32 | public static int roundCeilLog2(final int value) { 33 | // optimized variant of 1 << (32 - leading(val - 1)) 34 | // given 35 | // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) 36 | // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) 37 | // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) 38 | // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) 39 | // HIGH_BIT_32 >>> (-1 + leading(val - 1)) 40 | return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); 41 | } 42 | 43 | public static long roundCeilLog2(final long value) { 44 | // see logic documented above 45 | return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); 46 | } 47 | 48 | public static int roundFloorLog2(final int value) { 49 | // optimized variant of 1 << (31 - leading(val)) 50 | // given 51 | // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) 52 | // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) 53 | // HIGH_BIT_32 >> (31 - (31 - leading(val))) 54 | // HIGH_BIT_32 >> (31 - 31 + leading(val)) 55 | return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); 56 | } 57 | 58 | public static long roundFloorLog2(final long value) { 59 | // see logic documented above 60 | return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); 61 | } 62 | 63 | public static boolean isPowerOfTwo(final int n) { 64 | // 2^n has one bit 65 | // note: this rets true for 0 still 66 | return IntegerUtil.getTrailingBit(n) == n; 67 | } 68 | 69 | public static boolean isPowerOfTwo(final long n) { 70 | // 2^n has one bit 71 | // note: this rets true for 0 still 72 | return IntegerUtil.getTrailingBit(n) == n; 73 | } 74 | 75 | public static int getTrailingBit(final int n) { 76 | return -n & n; 77 | } 78 | 79 | public static long getTrailingBit(final long n) { 80 | return -n & n; 81 | } 82 | 83 | public static int trailingZeros(final int n) { 84 | return Integer.numberOfTrailingZeros(n); 85 | } 86 | 87 | public static int trailingZeros(final long n) { 88 | return Long.numberOfTrailingZeros(n); 89 | } 90 | 91 | // from hacker's delight (signed division magic value) 92 | public static int getDivisorMultiple(final long numbers) { 93 | return (int)(numbers >>> 32); 94 | } 95 | 96 | // from hacker's delight (signed division magic value) 97 | public static int getDivisorShift(final long numbers) { 98 | return (int)numbers; 99 | } 100 | 101 | // copied from hacker's delight (signed division magic value) 102 | // http://www.hackersdelight.org/hdcodetxt/magic.c.txt 103 | public static long getDivisorNumbers(final int d) { 104 | final int ad = IntegerUtil.branchlessAbs(d); 105 | 106 | if (ad < 2) { 107 | throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); 108 | } 109 | 110 | final int two31 = 0x80000000; 111 | final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour 112 | 113 | int p = 31; 114 | 115 | // all these variables are UNSIGNED! 116 | int t = two31 + (d >>> 31); 117 | int anc = t - 1 - t%ad; 118 | int q1 = (int)((two31 & mask)/(anc & mask)); 119 | int r1 = two31 - q1*anc; 120 | int q2 = (int)((two31 & mask)/(ad & mask)); 121 | int r2 = two31 - q2*ad; 122 | int delta; 123 | 124 | do { 125 | p = p + 1; 126 | q1 = 2*q1; // Update q1 = 2**p/|nc|. 127 | r1 = 2*r1; // Update r1 = rem(2**p, |nc|). 128 | if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) 129 | q1 = q1 + 1; 130 | r1 = r1 - anc; 131 | } 132 | q2 = 2*q2; // Update q2 = 2**p/|d|. 133 | r2 = 2*r2; // Update r2 = rem(2**p, |d|). 134 | if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) 135 | q2 = q2 + 1; 136 | r2 = r2 - ad; 137 | } 138 | delta = ad - r2; 139 | } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); 140 | 141 | int magicNum = q2 + 1; 142 | if (d < 0) { 143 | magicNum = -magicNum; 144 | } 145 | int shift = p - 32; 146 | return ((long)magicNum << 32) | shift; 147 | } 148 | 149 | public static int branchlessAbs(final int val) { 150 | // -n = -1 ^ n + 1 151 | final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 152 | return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 153 | } 154 | 155 | public static long branchlessAbs(final long val) { 156 | // -n = -1 ^ n + 1 157 | final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 158 | return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 159 | } 160 | 161 | //https://github.com/skeeto/hash-prospector for hash functions 162 | 163 | //score = ~590.47984224483832 164 | public static int hash0(int x) { 165 | x *= 0x36935555; 166 | x ^= x >>> 16; 167 | return x; 168 | } 169 | 170 | //score = ~310.01596637036749 171 | public static int hash1(int x) { 172 | x ^= x >>> 15; 173 | x *= 0x356aaaad; 174 | x ^= x >>> 17; 175 | return x; 176 | } 177 | 178 | public static int hash2(int x) { 179 | x ^= x >>> 16; 180 | x *= 0x7feb352d; 181 | x ^= x >>> 15; 182 | x *= 0x846ca68b; 183 | x ^= x >>> 16; 184 | return x; 185 | } 186 | 187 | public static int hash3(int x) { 188 | x ^= x >>> 17; 189 | x *= 0xed5ad4bb; 190 | x ^= x >>> 11; 191 | x *= 0xac4c1b51; 192 | x ^= x >>> 15; 193 | x *= 0x31848bab; 194 | x ^= x >>> 14; 195 | return x; 196 | } 197 | 198 | //score = ~365.79959673201887 199 | public static long hash1(long x) { 200 | x ^= x >>> 27; 201 | x *= 0xb24924b71d2d354bL; 202 | x ^= x >>> 28; 203 | return x; 204 | } 205 | 206 | //h2 hash 207 | public static long hash2(long x) { 208 | x ^= x >>> 32; 209 | x *= 0xd6e8feb86659fd93L; 210 | x ^= x >>> 32; 211 | x *= 0xd6e8feb86659fd93L; 212 | x ^= x >>> 32; 213 | return x; 214 | } 215 | 216 | public static long hash3(long x) { 217 | x ^= x >>> 45; 218 | x *= 0xc161abe5704b6c79L; 219 | x ^= x >>> 41; 220 | x *= 0xe3e5389aedbc90f7L; 221 | x ^= x >>> 56; 222 | x *= 0x1f9aba75a52db073L; 223 | x ^= x >>> 53; 224 | return x; 225 | } 226 | 227 | private IntegerUtil() { 228 | throw new RuntimeException(); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/main/java/io/papermc/paper/util/MCUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under https://github.com/PaperMC/Paper/blob/master/licenses/MIT.md 3 | */ 4 | 5 | package io.papermc.paper.util; 6 | 7 | import net.minecraft.util.math.ChunkPos; 8 | 9 | public class MCUtil { 10 | 11 | public static long getCoordinateKey(final int x, final int z) { 12 | return ((long)z << 32) | (x & 0xFFFFFFFFL); 13 | } 14 | 15 | public static long getCoordinateKey(final ChunkPos pair) { 16 | return ((long)pair.z << 32) | (pair.x & 0xFFFFFFFFL); 17 | } 18 | 19 | public static int getCoordinateX(final long key) { 20 | return (int)key; 21 | } 22 | 23 | public static int getCoordinateZ(final long key) { 24 | return (int)(key >>> 32); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/assets/vmp/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RelativityMC/VMP-fabric/e1c39a4f3ca41f198a6a9834d393e49f1d770de6/src/main/resources/assets/vmp/icon.png -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "vmp", 4 | "version": "${version}", 5 | 6 | "name": "Very Many Players", 7 | "description": "A Fabric mod designed to improve server performance at high playercounts", 8 | "authors": [ 9 | "ishland" 10 | ], 11 | "contact": { 12 | "sources": "https://github.com/ishlandbukkit/VMP-fabric", 13 | "issues": "https://github.com/ishlandbukkit/VMP-fabric/issues" 14 | }, 15 | 16 | "license": "MIT", 17 | "icon": "assets/vmp/icon.png", 18 | "environment": "*", 19 | "entrypoints": { 20 | "main": [ 21 | "com.ishland.vmp.VMPMod" 22 | ] 23 | }, 24 | "mixins": [ 25 | "vmp.mixins.json" 26 | ], 27 | 28 | "accessWidener": "vmp.accesswidener", 29 | 30 | "depends": { 31 | "fabricloader": ">=0.14.13", 32 | "minecraft": ">=1.20.2-beta.2", 33 | "java": ">=17" 34 | }, 35 | "breaks": { 36 | "optifabric": "*" 37 | }, 38 | 39 | "custom": { 40 | "lithium:options": { 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/vmp.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v1 named 2 | 3 | accessible class net/minecraft/server/world/ChunkTicketManager$TicketDistanceLevelPropagator 4 | accessible class net/minecraft/server/world/ChunkTicketManager$DistanceFromNearestPlayerTracker 5 | accessible class net/minecraft/server/world/ChunkTicketManager$NearbyChunkTicketUpdater 6 | accessible class net/minecraft/server/world/ServerChunkLoadingManager$EntityTracker 7 | accessible class net/minecraft/server/world/ServerChunkLoadingManager$TicketManager 8 | accessible class net/minecraft/world/SpawnDensityCapper$DensityCap 9 | accessible class net/minecraft/server/command/SpreadPlayersCommand$Pile 10 | 11 | extendable class net/minecraft/server/world/ChunkTicket 12 | extendable class net/minecraft/server/world/PlayerChunkWatchingManager 13 | 14 | accessible method net/minecraft/world/SpawnDensityCapper$DensityCap ()V 15 | accessible method net/minecraft/server/command/SpreadPlayersCommand$Pile ()V 16 | -------------------------------------------------------------------------------- /src/main/resources/vmp.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "com.ishland.vmp.mixins", 5 | "compatibilityLevel": "JAVA_16", 6 | "mixinPriority": 1050, 7 | "mixins": [ 8 | "access.IAbstractChunkHolder", 9 | "access.IChunkDeltaUpdateS2CPacket", 10 | "access.IChunkHolder", 11 | "access.IChunkTicket", 12 | "access.IClientConnection", 13 | "access.IEntityTrackerEntry", 14 | "access.IPointOfInterestSet", 15 | "access.IServerChunkManager", 16 | "access.IServerCommandSource", 17 | "access.IServerCommonNetworkHandler", 18 | "access.ISpreadPlayersCommandPile", 19 | "access.IThreadedAnvilChunkStorage", 20 | "access.IThreadedAnvilChunkStorageEntityTracker", 21 | "access.IThreadedAnvilChunkStorageTicketManager", 22 | "access.ITrackedPosition", 23 | "carpet.MixinEntityPlayerMPFake", 24 | "chunk.loading.MixinPointOfInterestStorage", 25 | "chunk.loading.async_chunk_on_player_login.MixinServerConfigurationNetworkHandler", 26 | "chunk.loading.commands.MixinCommandFunctionManager", 27 | "chunk.loading.commands.MixinSpreadPlayersCommand", 28 | "chunk.ticking.MixinServerWorld", 29 | "entity.move_zero_velocity.MixinEntity", 30 | "entitytracker.MixinThreadedAnvilChunkStorage", 31 | "entitytracker.MixinThreadedAnvilChunkStorageEntityTracker", 32 | "general.biome_access.MixinBiomeAccess", 33 | "general.biome_access.fast_chunk_access.MixinWorldView", 34 | "general.collections.MixinTypeFilterableList", 35 | "general.no_locking.MixinPalettedContainer", 36 | "general.spawn_density_cap.MixinSpawnDensityCapperDensityCap", 37 | "networking.avoid_deadlocks.MixinClientConnection", 38 | "networking.eventloops.MixinClientConnection", 39 | "networking.eventloops.MixinServerNetworkIo", 40 | "networking.no_flush.MixinClientConnection", 41 | "playerwatching.MixinChunkFilter", 42 | "playerwatching.MixinServerPlayerEntity", 43 | "playerwatching.MixinTACSCancelSending", 44 | "playerwatching.MixinTACSCancelSendingKrypton", 45 | "playerwatching.MixinThreadedAnvilChunkStorage", 46 | "playerwatching.optimize_nearby_entity_tracking_lookups.MixinEntityTrackerEntry", 47 | "playerwatching.optimize_nearby_entity_tracking_lookups.MixinServerPlayerEntity", 48 | "playerwatching.optimize_nearby_entity_tracking_lookups.MixinThreadedAnvilChunkStorage", 49 | "playerwatching.optimize_nearby_entity_tracking_lookups.MixinThreadedAnvilChunkStorageEntityTracker", 50 | "playerwatching.optimize_nearby_player_lookups.MixinMobEntity", 51 | "playerwatching.optimize_nearby_player_lookups.MixinServerWorld", 52 | "playerwatching.optimize_nearby_player_lookups.MixinSpawnDensityCapper", 53 | "playerwatching.optimize_nearby_player_lookups.MixinThreadedAnvilChunkStorage", 54 | "ticketsystem.ticketpropagator.MixinChunkTicketManager" 55 | ], 56 | "client": [ 57 | "timesource.MixinMinecraftClient" 58 | ], 59 | "plugin": "com.ishland.vmp.mixins.VMPMixinPlugin", 60 | "injectors": { 61 | "defaultRequire": 1 62 | } 63 | } 64 | --------------------------------------------------------------------------------