├── .github └── workflows │ ├── build_test.yml │ └── publish.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── extra_jar_def.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── javadoc.options ├── misc └── LICENSE_HEADER.txt ├── settings.gradle └── src ├── main ├── java │ └── alexiil │ │ └── mc │ │ └── lib │ │ └── net │ │ ├── ActiveConnection.java │ │ ├── BufferedConnection.java │ │ ├── CheckingNetByteBuf.java │ │ ├── DynamicNetId.java │ │ ├── DynamicNetLink.java │ │ ├── EnumNetSide.java │ │ ├── IMsgCtx.java │ │ ├── IMsgReadCtx.java │ │ ├── IMsgWriteCtx.java │ │ ├── InternalMsgUtil.java │ │ ├── InvalidInputDataException.java │ │ ├── LibNetworkStack.java │ │ ├── LibNetworkStackClient.java │ │ ├── MessageContext.java │ │ ├── MsgUtil.java │ │ ├── NetByteBuf.java │ │ ├── NetIdBase.java │ │ ├── NetIdData.java │ │ ├── NetIdDataK.java │ │ ├── NetIdPath.java │ │ ├── NetIdSeparate.java │ │ ├── NetIdSignal.java │ │ ├── NetIdSignalK.java │ │ ├── NetIdTyped.java │ │ ├── NetKeyMapper.java │ │ ├── NetObjectCache.java │ │ ├── NetObjectCacheBase.java │ │ ├── NetObjectCacheSimple.java │ │ ├── ParentDynamicNetId.java │ │ ├── ParentNetId.java │ │ ├── ParentNetIdBase.java │ │ ├── ParentNetIdCast.java │ │ ├── ParentNetIdDuel.java │ │ ├── ParentNetIdDuelDirect.java │ │ ├── ParentNetIdExtractor.java │ │ ├── ParentNetIdSingle.java │ │ ├── ResolvedDynamicNetId.java │ │ ├── ResolvedNetId.java │ │ ├── ResolvedParentNetId.java │ │ ├── SingleConnection.java │ │ ├── TreeNetIdBase.java │ │ ├── impl │ │ ├── ActiveClientConnection.java │ │ ├── ActiveMinecraftConnection.java │ │ ├── ActiveServerConnection.java │ │ ├── BlockEntityInitialData.java │ │ ├── CompactDataPacketToClient.java │ │ ├── CompactDataPacketToServer.java │ │ ├── CoreMinecraftNetUtil.java │ │ ├── IPacketCustomId.java │ │ ├── McNetworkStack.java │ │ └── package-info.java │ │ ├── mixin │ │ ├── api │ │ │ ├── IBlockEntityInitialData.java │ │ │ ├── INetworkStateMixin.java │ │ │ ├── IPacketHandlerMixin.java │ │ │ ├── IThreadedAnvilChunkStorageMixin.java │ │ │ ├── ITickCounterMixin.java │ │ │ └── package-info.java │ │ └── impl │ │ │ ├── ChunkHolderMixin.java │ │ │ ├── MinecraftClientMixin.java │ │ │ ├── NetworkStateMixin.java │ │ │ ├── PacketHandlerMixin.java │ │ │ ├── RenderTickCounterMixin.java │ │ │ ├── ServerPlayNetworkHandlerAccessor.java │ │ │ └── ThreadedAnvilChunkStorageMixin.java │ │ └── package-info.java └── resources │ ├── assets │ └── libnetworkstack │ │ ├── icon.png │ │ └── icon_1024.png │ ├── changelog │ ├── 0.1.0.txt │ ├── 0.1.1.txt │ ├── 0.10.0.txt │ ├── 0.2.1.txt │ ├── 0.3.0.txt │ ├── 0.3.4.txt │ ├── 0.3.6.txt │ ├── 0.4.0.txt │ ├── 0.4.1.txt │ ├── 0.4.2.txt │ ├── 0.4.3.txt │ ├── 0.4.4.txt │ ├── 0.4.7.txt │ ├── 0.4.8.txt │ ├── 0.5.0.txt │ ├── 0.6.0.txt │ ├── 0.6.3.txt │ ├── 0.7.0.txt │ ├── 0.7.1.txt │ ├── 0.7.2.txt │ ├── 0.8.0.txt │ └── 0.9.0.txt │ ├── fabric.mod.json │ ├── libnetworkstack.client.json │ └── libnetworkstack.common.json └── test └── java └── alexiil └── mc └── lib └── net └── test ├── McBufferTester.java └── SimpleNetTester.java /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | name: Validation 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: '**:**' 7 | 8 | jobs: 9 | prep: 10 | runs-on: ubuntu-latest 11 | name: 'Work' 12 | outputs: 13 | did_tasks: ${{ steps.tasks.conclusion }} 14 | did_license: ${{ steps.license.conclusion }} 15 | did_test: ${{ steps.test.conclusion }} 16 | did_javadoc: ${{ steps.javadoc.conclusion }} 17 | did_build: ${{ steps.build.conclusion }} 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/setup-java@v2.1.0 21 | with: 22 | distribution: 'adopt-hotspot' 23 | java-version: '17' 24 | - id: tasks 25 | name: 'Setup' 26 | run: './gradlew tasks' 27 | - id: 'license' 28 | name: 'Check License' 29 | run: './gradlew checkLicense' 30 | - id: 'test' 31 | name: 'Test' 32 | run: './gradlew test' 33 | - id: 'javadoc' 34 | name: 'Javadoc' 35 | run: './gradlew javadoc' 36 | - id: 'build' 37 | name: 'Build' 38 | run: './gradlew build' 39 | output_tasks: 40 | name: 'Check Buildscript' 41 | runs-on: ubuntu-latest 42 | needs: prep 43 | if: always() 44 | steps: 45 | - name: 'Check Tasks' 46 | run: | 47 | echo "${{ needs.prep.outputs.did_tasks }}" 48 | if [[ ${{ needs.prep.outputs.did_tasks }} != "success" ]]; then 49 | echo "Look at the 'Work' job to see what went wrong!" 50 | exit 1 51 | fi 52 | output_license: 53 | name: 'Check License' 54 | runs-on: ubuntu-latest 55 | needs: [prep, output_tasks] 56 | if: always() 57 | steps: 58 | - name: 'Check License' 59 | run: | 60 | echo "${{ needs.prep.outputs.did_license }}" 61 | if [[ ${{ needs.prep.outputs.did_license }} != "success" ]]; then 62 | echo "Look at the 'Work' job to see what went wrong!" 63 | exit 1 64 | fi 65 | output_test: 66 | name: 'Run Tests' 67 | runs-on: ubuntu-latest 68 | needs: [prep, output_license] 69 | if: always() 70 | steps: 71 | - name: 'Check Test' 72 | run: | 73 | echo "${{ needs.prep.outputs.did_test }}" 74 | if [[ ${{ needs.prep.outputs.did_test }} != "success" ]]; then 75 | echo "Look at the 'Work' job to see what went wrong!" 76 | exit 1 77 | fi 78 | output_javadoc: 79 | name: 'Generate Javadoc' 80 | runs-on: ubuntu-latest 81 | needs: [prep, output_test] 82 | if: always() 83 | steps: 84 | - name: 'Check Javadocs' 85 | run: | 86 | echo "${{ needs.prep.outputs.did_javadoc }}" 87 | if [[ ${{ needs.prep.outputs.did_javadoc }} != "success" ]]; then 88 | echo "Look at the 'Work' job to see what went wrong!" 89 | exit 1 90 | fi 91 | output_build: 92 | name: 'Build Jars' 93 | runs-on: ubuntu-latest 94 | needs: [prep, output_javadoc] 95 | if: always() 96 | steps: 97 | - name: 'Check Build' 98 | run: | 99 | echo "${{ needs.prep.outputs.did_build }}" 100 | if [[ ${{ needs.prep.outputs.did_build }} != "success" ]]; then 101 | echo "Look at the 'Work' job to see what went wrong!" 102 | exit 1 103 | fi 104 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: [workflow_dispatch] # Manual trigger 4 | 5 | jobs: 6 | prep: 7 | runs-on: ubuntu-latest 8 | name: 'Work' 9 | outputs: 10 | did_tasks: ${{ steps.tasks.conclusion }} 11 | did_license: ${{ steps.license.conclusion }} 12 | did_test: ${{ steps.test.conclusion }} 13 | did_javadoc: ${{ steps.javadoc.conclusion }} 14 | did_build: ${{ steps.build.conclusion }} 15 | did_publish: ${{ steps.publish.conclusion }} 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-java@v2.1.0 19 | with: 20 | distribution: 'adopt-hotspot' 21 | java-version: '17' 22 | - id: tasks 23 | name: 'Setup' 24 | run: './gradlew tasks' 25 | - id: 'license' 26 | name: 'Check License' 27 | run: './gradlew checkLicense' 28 | - id: 'test' 29 | name: 'Test' 30 | run: './gradlew test' 31 | - id: 'javadoc' 32 | name: 'Javadoc' 33 | run: './gradlew javadoc' 34 | - id: 'build' 35 | name: 'Build' 36 | run: './gradlew build' 37 | - id: 'publish' 38 | name: 'Publish' 39 | run: './gradlew publish uploadJavadoc' 40 | env: 41 | UPLOAD_MAVEN_URL: ${{ secrets.UPLOAD_MAVEN_URL }} 42 | UPLOAD_PASSWORD: ${{ secrets.UPLOAD_PASSWORD }} 43 | UPLOAD_USERNAME: ${{ secrets.UPLOAD_USERNAME }} 44 | output_tasks: 45 | name: 'Check Buildscript' 46 | runs-on: ubuntu-latest 47 | needs: prep 48 | if: always() 49 | steps: 50 | - name: 'Check Tasks' 51 | run: | 52 | echo "${{ needs.prep.outputs.did_tasks }}" 53 | if [[ ${{ needs.prep.outputs.did_tasks }} != "success" ]]; then 54 | echo "Look at the 'Work' job to see what went wrong!" 55 | exit 1 56 | fi 57 | output_license: 58 | name: 'Check License' 59 | runs-on: ubuntu-latest 60 | needs: [prep, output_tasks] 61 | if: always() 62 | steps: 63 | - name: 'Check License' 64 | run: | 65 | echo "${{ needs.prep.outputs.did_license }}" 66 | if [[ ${{ needs.prep.outputs.did_license }} != "success" ]]; then 67 | echo "Look at the 'Work' job to see what went wrong!" 68 | exit 1 69 | fi 70 | output_test: 71 | name: 'Run Tests' 72 | runs-on: ubuntu-latest 73 | needs: [prep, output_license] 74 | if: always() 75 | steps: 76 | - name: 'Check Test' 77 | run: | 78 | echo "${{ needs.prep.outputs.did_test }}" 79 | if [[ ${{ needs.prep.outputs.did_test }} != "success" ]]; then 80 | echo "Look at the 'Work' job to see what went wrong!" 81 | exit 1 82 | fi 83 | output_javadoc: 84 | name: 'Generate Javadoc' 85 | runs-on: ubuntu-latest 86 | needs: [prep, output_test] 87 | if: always() 88 | steps: 89 | - name: 'Check Javadocs' 90 | run: | 91 | echo "${{ needs.prep.outputs.did_javadoc }}" 92 | if [[ ${{ needs.prep.outputs.did_javadoc }} != "success" ]]; then 93 | echo "Look at the 'Work' job to see what went wrong!" 94 | exit 1 95 | fi 96 | output_build: 97 | name: 'Build Jars' 98 | runs-on: ubuntu-latest 99 | needs: [prep, output_javadoc] 100 | if: always() 101 | steps: 102 | - name: 'Check Build' 103 | run: | 104 | echo "${{ needs.prep.outputs.did_build }}" 105 | if [[ ${{ needs.prep.outputs.did_build }} != "success" ]]; then 106 | echo "Look at the 'Work' job to see what went wrong!" 107 | exit 1 108 | fi 109 | output_publish: 110 | name: 'Publish Jars' 111 | runs-on: ubuntu-latest 112 | needs: [prep, output_build] 113 | if: always() 114 | steps: 115 | - name: 'Check Publishing' 116 | run: | 117 | echo "${{ needs.prep.outputs.did_publish }}" 118 | if [[ ${{ needs.prep.outputs.did_publish }} != "success" ]]; then 119 | echo "Look at the 'Work' job to see what went wrong!" 120 | exit 1 121 | fi 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | 7 | # idea 8 | 9 | .idea/ 10 | *.iml 11 | *.ipr 12 | *.iws 13 | 14 | # vscode 15 | 16 | .settings/ 17 | .vscode/ 18 | bin/ 19 | .classpath 20 | .project 21 | *.launch 22 | 23 | # fabric 24 | 25 | run/ 26 | logs/ 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | 5 | install: ./gradlew build publishToMavenLocal 6 | script: ./gradlew test 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LibNetworkStack 2 | 3 | This is a library mod for the [Fabric](https://fabricmc.net/) API, based around [Minecraft](https://minecraft.net). 4 | 5 | ## Maven 6 | 7 | Currently you can use this by adding this to your build.gradle: 8 | 9 | ``` 10 | repositories { 11 | maven { 12 | name = "AlexIIL" 13 | url = "https://maven.alexiil.uk/" 14 | } 15 | } 16 | 17 | dependencies { 18 | modImplementation "alexiil.mc.lib:libnetworkstack-base:0.7.1" 19 | } 20 | ``` 21 | 22 | Please use the javadoc list [here](https://alexiil.uk/javadoc) to determine the right LNS version for your minecraft version. 23 | 24 | ## Getting Started 25 | 26 | You can either look at the [wiki](https://github.com/AlexIIL/LibNetworkStack/wiki) for a brief overview, or look at [SimplePipes](https://github.com/AlexIIL/SimplePipes) source code for a use-case mod that uses this. To get help you can either open an issue here, or ping AlexIIL on the fabric or CottonMC discord servers. 27 | 28 | ## A simple example 29 | 30 | For example if you wanted to send data from your custom BlockEntity called "ElectricFurnaceBlockEntity", you would do this: 31 | 32 | ```java 33 | 34 | import alexiil.mc.lib.net.ActiveConnection; 35 | import alexiil.mc.lib.net.IMsgReadCtx; 36 | import alexiil.mc.lib.net.IMsgWriteCtx; 37 | import alexiil.mc.lib.net.InvalidInputDataException; 38 | import alexiil.mc.lib.net.NetByteBuf; 39 | import alexiil.mc.lib.net.NetIdDataK; 40 | import alexiil.mc.lib.net.NetIdDataK.IMsgDataWriterK; 41 | import alexiil.mc.lib.net.NetIdSignalK; 42 | import alexiil.mc.lib.net.ParentNetIdSingle; 43 | import alexiil.mc.lib.net.impl.CoreMinecraftNetUtil; 44 | import alexiil.mc.lib.net.impl.McNetworkStack; 45 | 46 | public class ElectricFurnaceBlockEntity extends BlockEntity { 47 | // All base code left out 48 | public static final ParentNetIdSingle NET_PARENT; 49 | public static final NetIdDataK ID_CHANGE_BRIGHTNESS; 50 | public static final NetIdSignalK ID_TURN_ON, ID_TURN_OFF; 51 | 52 | static { 53 | NET_PARENT = McNetworkStack.BLOCK_ENTITY.subType( 54 | // Assuming the modid is "electrical_nightmare" 55 | ElectricFurnaceBlockEntity.class, "electrical_nightmare:electric_furnace" 56 | ); 57 | // We don't need to re-specify the modid for this because the parent already did 58 | // and we don't expect any other mods to add new id's. 59 | ID_CHANGE_BRIGHTNESS = NET_PARENT.idData("CHANGE_BRIGHTNESS").setReceiver( 60 | ElectricFurnaceBlockEntity::receiveBrightnessChange 61 | ); 62 | ID_TURN_ON = NET_PARENT.idSignal("TURN_ON").setReceiver(ElectricFurnaceBlockEntity::receiveTurnOn); 63 | ID_TURN_OFF = NET_PARENT.idSignal("TURN_OFF").setReceiver(ElectricFurnaceBlockEntity::receiveTurnOff); 64 | } 65 | 66 | /** Used for rendering - somehow. */ 67 | public int clientBrightness; 68 | public boolean clientIsOn; 69 | 70 | public ElectricFurnaceBlockEntity() { 71 | super(null); 72 | } 73 | 74 | /** Sends the new brightness, to be rendered on the front of the block. 75 | * This should be called on the server side. 76 | * 77 | * @param newBrightness A value between 0 and 255. */ 78 | protected final void sendBrightnessChange(int newBrightness) { 79 | for (ActiveConnection connection : CoreMinecraftNetUtil.getPlayersWatching(getWorld(), getPos())) { 80 | ID_CHANGE_BRIGHTNESS.send(connection, this, new IMsgDataWriterK() { 81 | 82 | // In your own code you probably want to change this to a lambda 83 | // but the full types are used here for clarity. 84 | 85 | @Override 86 | public void write(ElectricFurnaceBlockEntity be, NetByteBuf buf, IMsgWriteCtx ctx) { 87 | ctx.assertServerSide(); 88 | 89 | buf.writeByte(newBrightness); 90 | } 91 | }); 92 | } 93 | } 94 | 95 | /** Sends the new on/off state, to be rendered on the front of the block. 96 | * This should be called on the server side. */ 97 | protected final void sendSwitchState(boolean isOn) { 98 | for (ActiveConnection connection : CoreMinecraftNetUtil.getPlayersWatching(getWorld(), getPos())) { 99 | (isOn ? ID_TURN_ON : ID_TURN_OFF).send(connection, this); 100 | } 101 | } 102 | 103 | protected void receiveBrightnessChange(NetByteBuf buf, IMsgReadCtx ctx) throws InvalidInputDataException { 104 | // Ensure that this was sent by the server, to the client (and that we are on the client side) 105 | ctx.assertClientSide(); 106 | 107 | this.clientBrightness = buf.readUnsignedByte(); 108 | } 109 | 110 | protected void receiveTurnOn(IMsgReadCtx ctx) throws InvalidInputDataException { 111 | // Ensure that this was sent by the server, to the client (and that we are on the client side) 112 | ctx.assertClientSide(); 113 | 114 | this.clientIsOn = true; 115 | } 116 | 117 | protected void receiveTurnOff(IMsgReadCtx ctx) throws InvalidInputDataException { 118 | // Ensure that this was sent by the server, to the client (and that we are on the client side) 119 | ctx.assertClientSide(); 120 | 121 | this.clientIsOn = false; 122 | } 123 | } 124 | 125 | ``` 126 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '1.2-SNAPSHOT' 3 | id 'org.cadixdev.licenser' version '0.6.1' 4 | } 5 | 6 | apply plugin: 'maven-publish' // for uploading to a maven repo 7 | 8 | sourceCompatibility = JavaVersion.VERSION_17 9 | targetCompatibility = JavaVersion.VERSION_17 10 | 11 | archivesBaseName = "LibNetworkStack" 12 | version = "0.10.1-pre.2" 13 | 14 | license { 15 | header = project.file('misc/LICENSE_HEADER.txt'); 16 | newLine = false; 17 | } 18 | 19 | loom { 20 | } 21 | 22 | dependencies { 23 | minecraft "com.mojang:minecraft:1.20" 24 | mappings "net.fabricmc:yarn:1.20+build.1:v2" 25 | modCompileOnly "net.fabricmc:fabric-loader:0.14.21" 26 | modLocalRuntime "net.fabricmc:fabric-loader:0.14.21" 27 | 28 | //Fabric api 29 | modCompileOnly "net.fabricmc.fabric-api:fabric-api:0.83.0+1.20" 30 | modLocalRuntime "net.fabricmc.fabric-api:fabric-api:0.83.0+1.20" 31 | 32 | // Misc 33 | compileOnly "com.google.code.findbugs:jsr305:3.0.1" 34 | testImplementation "junit:junit:4.13.1" 35 | } 36 | 37 | compileJava { 38 | options.compilerArgs << "-Xmaxerrs" << "2000" 39 | options.compilerArgs << "-Xmaxwarns" << "2" 40 | options.compilerArgs << "-Xlint:all" 41 | options.compilerArgs << "-Xdiags:verbose" 42 | } 43 | 44 | javadoc { 45 | destinationDir file(new File(System.getenv("JAVADOC_DIR") ?: "$projectDir/build/javadoc", "$version")); 46 | exclude "alexiil/mc/lib/net/mixin"; 47 | options.optionFiles << file('javadoc.options'); 48 | } 49 | 50 | build.dependsOn(javadoc); 51 | 52 | // ensure that the encoding is set to UTF-8, no matter what the system default is 53 | // this fixes some edge cases with special characters not displaying correctly 54 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 55 | tasks.withType(JavaCompile) { 56 | options.encoding = "UTF-8" 57 | 58 | // Minecraft 1.18 upwards uses Java 17. 59 | it.options.release = 17 60 | } 61 | 62 | java.withSourcesJar() 63 | 64 | import java.net.Authenticator; 65 | import java.net.PasswordAuthentication; 66 | import java.net.http.HttpClient; 67 | import java.net.http.HttpRequest; 68 | import java.net.http.HttpResponse; 69 | 70 | if (System.getenv("UPLOAD_MAVEN_URL") != null) { 71 | task packageJavadoc(type: Zip, dependsOn: javadoc) { 72 | from javadoc.destinationDir 73 | archiveFileName = "libnetworkstack-javadoc-" + version + ".zip" 74 | } 75 | 76 | task uploadJavadoc(dependsOn: packageJavadoc) { 77 | doLast { 78 | HttpClient cl = HttpClient.newBuilder().authenticator(new Authenticator() { 79 | @Override 80 | protected PasswordAuthentication getPasswordAuthentication() { 81 | return new PasswordAuthentication(System.getenv("UPLOAD_USERNAME"), System.getenv("UPLOAD_PASSWORD").toCharArray()); 82 | } 83 | }).build(); 84 | java.io.File file = packageJavadoc.outputs.files.singleFile; 85 | java.nio.file.Path path = file.toPath(); 86 | HttpRequest put = HttpRequest.newBuilder() 87 | .PUT(HttpRequest.BodyPublishers.ofFile(path)) 88 | .uri(new URI(System.getenv("UPLOAD_MAVEN_URL") + "/tmp-javadocs/" + file.getName())) 89 | .build(); 90 | HttpResponse response = cl.send(put, HttpResponse.BodyHandlers.ofString()); 91 | if ((int)(response.statusCode() / 100) != 2) { 92 | println(response.statusCode()); 93 | println(response.body()); 94 | throw new Error("Upload Javadoc Zip Failed!"); 95 | } 96 | } 97 | } 98 | } 99 | 100 | publishing { 101 | repositories { 102 | if (System.getenv("UPLOAD_MAVEN_URL") != null) { 103 | maven { 104 | url System.getenv("UPLOAD_MAVEN_URL"); 105 | credentials { 106 | username System.getenv("UPLOAD_USERNAME"); 107 | password System.getenv("UPLOAD_PASSWORD"); 108 | } 109 | } 110 | } else { 111 | maven { 112 | url System.getenv("MAVEN_DIR") ?: "$projectDir/build/maven" 113 | } 114 | } 115 | } 116 | } 117 | 118 | // ##################### 119 | // 120 | // Extra jar section 121 | // 122 | // ##################### 123 | 124 | apply from: "extra_jar_def.gradle" 125 | 126 | ext.mainName = "libnetworkstack" 127 | ext.mavenGroupId = "alexiil.mc.lib"; 128 | 129 | ext.extra_jar_def__optimised_compression = true; 130 | ext.extra_jar_def__common_manifest.put(null, ['Sealed': 'true']); 131 | 132 | ext.generateJar("base", ["**"], [], true); 133 | 134 | tasks.withType(AbstractArchiveTask) { 135 | preserveFileTimestamps = false 136 | reproducibleFileOrder = true 137 | } 138 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle keeps running out of heap space 2 | org.gradle.jvmargs=-Xmx1G 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexIIL/LibNetworkStack/6fe7c8755cbd5bab03a7ccab0a1ab59a94b57851/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.1.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /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/HEAD/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 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 134 | 135 | Please set the JAVA_HOME variable in your environment to match the 136 | location of your Java installation." 137 | fi 138 | 139 | # Increase the maximum file descriptors if we can. 140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 141 | case $MAX_FD in #( 142 | max*) 143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 144 | # shellcheck disable=SC3045 145 | MAX_FD=$( ulimit -H -n ) || 146 | warn "Could not query maximum file descriptor limit" 147 | esac 148 | case $MAX_FD in #( 149 | '' | soft) :;; #( 150 | *) 151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 152 | # shellcheck disable=SC3045 153 | ulimit -n "$MAX_FD" || 154 | warn "Could not set maximum file descriptor limit to $MAX_FD" 155 | esac 156 | fi 157 | 158 | # Collect all arguments for the java command, stacking in reverse order: 159 | # * args from the command line 160 | # * the main class name 161 | # * -classpath 162 | # * -D...appname settings 163 | # * --module-path (only if needed) 164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 165 | 166 | # For Cygwin or MSYS, switch paths to Windows format before running java 167 | if "$cygwin" || "$msys" ; then 168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 170 | 171 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 172 | 173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 174 | for arg do 175 | if 176 | case $arg in #( 177 | -*) false ;; # don't mess with options #( 178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 179 | [ -e "$t" ] ;; #( 180 | *) false ;; 181 | esac 182 | then 183 | arg=$( cygpath --path --ignore --mixed "$arg" ) 184 | fi 185 | # Roll the args list around exactly as many times as the number of 186 | # args, so each arg winds up back in the position where it started, but 187 | # possibly modified. 188 | # 189 | # NB: a `for` loop captures its iteration list before it begins, so 190 | # changing the positional parameters here affects neither the number of 191 | # iterations, nor the values presented in `arg`. 192 | shift # remove old arg 193 | set -- "$@" "$arg" # push replacement arg 194 | done 195 | fi 196 | 197 | 198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 200 | 201 | # Collect all arguments for the java command; 202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 203 | # shell script including quotes and variable substitutions, so put them in 204 | # double quotes to make sure that they get re-expanded; and 205 | # * put everything else in single quotes, so that it's not re-expanded. 206 | 207 | set -- \ 208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 209 | -classpath "$CLASSPATH" \ 210 | org.gradle.wrapper.GradleWrapperMain \ 211 | "$@" 212 | 213 | # Stop when "xargs" is not available. 214 | if ! command -v xargs >/dev/null 2>&1 215 | then 216 | die "xargs is not available" 217 | fi 218 | 219 | # Use "xargs" to parse quoted args. 220 | # 221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 222 | # 223 | # In Bash we could simply go: 224 | # 225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 226 | # set -- "${ARGS[@]}" "$@" 227 | # 228 | # but POSIX shell has neither arrays nor command substitution, so instead we 229 | # post-process each arg (as a line of input to sed) to backslash-escape any 230 | # character that might be a shell metacharacter, then use eval to reverse 231 | # that process (while maintaining the separation between arguments), and wrap 232 | # the whole thing up as a single "set" statement. 233 | # 234 | # This will of course break if any of these variables contains a newline or 235 | # an unmatched quote. 236 | # 237 | 238 | eval "set -- $( 239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 240 | xargs -n1 | 241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 242 | tr '\n' ' ' 243 | )" '"$@"' 244 | 245 | exec "$JAVACMD" "$@" 246 | -------------------------------------------------------------------------------- /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 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /javadoc.options: -------------------------------------------------------------------------------- 1 | -use 2 | -Xdoclint:syntax 3 | -Xmaxwarns 1 4 | -------------------------------------------------------------------------------- /misc/LICENSE_HEADER.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 AlexIIL 2 | 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | maven { 5 | name = 'Fabric' 6 | url = 'https://maven.fabricmc.net/' 7 | } 8 | gradlePluginPortal() 9 | } 10 | } 11 | rootProject.name="LibNetworkStack" 12 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ActiveConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import javax.annotation.Nullable; 16 | 17 | import net.minecraft.entity.player.PlayerEntity; 18 | 19 | import alexiil.mc.lib.net.impl.ActiveMinecraftConnection; 20 | 21 | import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 22 | import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 23 | import it.unimi.dsi.fastutil.objects.Object2IntMap; 24 | import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; 25 | 26 | /** An active game connection to a single receiver (and with a single sender). */ 27 | public abstract class ActiveConnection { 28 | 29 | final ParentNetId rootId; 30 | 31 | /** Map of int -> net_id for reading. */ 32 | final List readMapIds = new ArrayList<>(); 33 | final Object2IntMap writeMapIds = new Object2IntOpenHashMap<>(); 34 | 35 | final Map, NetObjectCacheBase.Data> caches = new HashMap<>(); 36 | 37 | final Int2ObjectMap receivedTraceStringSegments; 38 | final Int2ObjectMap receivedTraceLines; 39 | final Int2ObjectMap receivedJoinedTraces; 40 | 41 | final StringTraceSegment rootTraceSegment; 42 | 43 | int allocatedStringSegemnts; 44 | int allocatedTraceSegemnts; 45 | int allocatedJoinedTraces; 46 | 47 | /** The next ID to use for *writing*. Note that the other side of the connection will tell *us* what ID's to 48 | * allocate. */ 49 | int nextFreeId = InternalMsgUtil.COUNT_HARDCODED_IDS; 50 | 51 | boolean sendTypes = LibNetworkStack.CONFIG_RECORD_TYPES; 52 | 53 | /** As stacktraces can leak (potentially) information about the server modset (maybe?) and it's quite expensive this 54 | * values should always be AND'd with {@link LibNetworkStack#CONFIG_RECORD_STACKTRACES} to ensure that both sides 55 | * are okay sending information. */ 56 | boolean sendStacktraces = isDebuggingConnection(); 57 | 58 | int lastReceivedTypesCount; 59 | NetByteBuf lastReceivedTypes; 60 | MultiTraceLines lastReceivedStacktrace; 61 | 62 | public ActiveConnection(ParentNetId rootId) { 63 | this.rootId = rootId; 64 | for (int i = 0; i < InternalMsgUtil.COUNT_HARDCODED_IDS; i++) { 65 | readMapIds.add(null); 66 | } 67 | 68 | if (LibNetworkStack.CONFIG_RECORD_STACKTRACES) { 69 | receivedTraceStringSegments = new Int2ObjectOpenHashMap<>(); 70 | receivedTraceLines = new Int2ObjectOpenHashMap<>(); 71 | receivedJoinedTraces = new Int2ObjectOpenHashMap<>(); 72 | rootTraceSegment = new StringTraceSegment(0, null, null, null); 73 | } else { 74 | receivedTraceStringSegments = null; 75 | receivedTraceLines = null; 76 | receivedJoinedTraces = null; 77 | rootTraceSegment = null; 78 | } 79 | } 80 | 81 | protected boolean isDebuggingConnection() { 82 | return false; 83 | } 84 | 85 | public final void postConstruct() { 86 | if (LibNetworkStack.CONFIG_RECORD_TYPES) { 87 | NetByteBuf data = NetByteBuf.buffer(1); 88 | data.writeVarInt(InternalMsgUtil.ID_INTERNAL_REQUEST_DEBUG_TYPES); 89 | sendPacket(data, InternalMsgUtil.ID_INTERNAL_REQUEST_DEBUG_TYPES, null, 0); 90 | } 91 | if (LibNetworkStack.CONFIG_RECORD_STACKTRACES) { 92 | NetByteBuf data = NetByteBuf.buffer(1); 93 | data.writeVarInt(InternalMsgUtil.ID_INTERNAL_REQUEST_STACKTRACES); 94 | sendPacket(data, InternalMsgUtil.ID_INTERNAL_REQUEST_STACKTRACES, null, 0); 95 | } 96 | } 97 | 98 | /** @return The "side" of this connection. If this is an {@link ActiveMinecraftConnection} then this will be CLIENT 99 | * both when writing client to server packets, and when reading packets sent from the server. (And SERVER 100 | * both when writing server to client packets, and when reading client to server packets). Other connection 101 | * types might throw an exception if they reuse LNS for a non-standard minecraft connection. */ 102 | public EnumNetSide getNetSide() { 103 | throw new UnsupportedOperationException("This type of ActiveConnection does not support net-sides."); 104 | } 105 | 106 | /** @return The Minecraft {@link PlayerEntity} for this connection. Throws an error if this is not a 107 | * {@link ActiveMinecraftConnection}. */ 108 | public PlayerEntity getPlayer() { 109 | throw new UnsupportedOperationException("This type of ActiveConnection does not support players."); 110 | } 111 | 112 | /** @param packetId The packet ID that has been written out to the first int of the given buffer. 113 | * @param netId The {@link NetIdBase} that is being written out. Will be null if the packet ID is one of the 114 | * internal packets. 115 | * @param priority The priority for the packet. Will be either 0 or a negative number. */ 116 | protected abstract void sendPacket(NetByteBuf data, int packetId, @Nullable NetIdBase netId, int priority); 117 | 118 | /** Flushes any packet queues, if present. In LNS itself this doesn't do anything unless this is a 119 | * {@link BufferedConnection} */ 120 | public void flushQueue() { 121 | // NO-OP 122 | } 123 | 124 | public void onReceiveRawData(NetByteBuf data) throws InvalidInputDataException { 125 | InternalMsgUtil.onReceive(this, data); 126 | } 127 | 128 | public NetByteBuf allocBuffer() { 129 | return NetByteBuf.buffer(); 130 | } 131 | 132 | public NetByteBuf allocBuffer(int initialCapacity) { 133 | return NetByteBuf.buffer(initialCapacity); 134 | } 135 | 136 | NetObjectCacheBase.Data getCacheData(NetObjectCacheBase cache) { 137 | // Nothing we can do about this warning without storing it directly in the cache 138 | return (NetObjectCacheBase.Data) caches.computeIfAbsent(cache, c -> c.new Data(this)); 139 | } 140 | 141 | enum StringTraceSeparator { 142 | DOT('.'), 143 | SLASH('/'), 144 | DOLLAR('$'); 145 | 146 | final char separator; 147 | 148 | StringTraceSeparator(char separator) { 149 | this.separator = separator; 150 | } 151 | 152 | @Nullable 153 | static StringTraceSeparator from(char c) { 154 | switch (c) { 155 | case '.': 156 | return DOT; 157 | case '/': 158 | return SLASH; 159 | default: 160 | return null; 161 | } 162 | } 163 | } 164 | 165 | static final class StringTraceSegment { 166 | final int id; 167 | final StringTraceSegment parent; 168 | final StringTraceSeparator separator; 169 | final String str; 170 | 171 | final Map dotChildren = new HashMap<>(); 172 | final Map slashChildren = new HashMap<>(); 173 | final Map dollarChildren = new HashMap<>(); 174 | final Int2ObjectMap lineChildren = new Int2ObjectOpenHashMap<>(); 175 | 176 | public StringTraceSegment(int id, StringTraceSegment parent, StringTraceSeparator separator, String str) { 177 | this.id = id; 178 | this.separator = separator; 179 | this.str = str; 180 | this.parent = parent == null ? null : parent.separator == null ? null : parent; 181 | if (parent != null) { 182 | parent.getCharChild(separator).put(str, this); 183 | } 184 | } 185 | 186 | Map getCharChild(StringTraceSeparator sep) { 187 | switch (sep) { 188 | case DOT: 189 | return dotChildren; 190 | case SLASH: 191 | return slashChildren; 192 | case DOLLAR: 193 | return dollarChildren; 194 | default: 195 | throw new IllegalArgumentException("Unknown StringTraceSeparator " + sep); 196 | } 197 | } 198 | 199 | @Override 200 | public String toString() { 201 | if (parent == null) { 202 | return str; 203 | } 204 | return parent.toString() + separator.separator + str; 205 | } 206 | } 207 | 208 | static final class SingleTraceLine { 209 | final int id; 210 | final StringTraceSegment str; 211 | final int lineNumber; 212 | 213 | final Map children = new HashMap<>(); 214 | 215 | public SingleTraceLine(int id, StringTraceSegment str, int lineNumber) { 216 | this.id = id; 217 | this.str = str; 218 | this.lineNumber = lineNumber; 219 | 220 | str.lineChildren.put(lineNumber, this); 221 | } 222 | 223 | @Override 224 | public String toString() { 225 | return str + "():" + lineNumber; 226 | } 227 | } 228 | 229 | static final class MultiTraceLines { 230 | final int id; 231 | final MultiTraceLines parent; 232 | final SingleTraceLine line; 233 | 234 | public MultiTraceLines(int id, MultiTraceLines parent, SingleTraceLine line) { 235 | this.id = id; 236 | this.parent = parent; 237 | this.line = line; 238 | 239 | line.children.put(parent, this); 240 | } 241 | 242 | @Override 243 | public String toString() { 244 | if (parent == null) { 245 | return line.toString(); 246 | } else { 247 | return line.toString() + "\n at " + parent.toString(); 248 | } 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/BufferedConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import alexiil.mc.lib.net.BufferedConnection.BufferedPacketInfo; 11 | import java.util.ArrayDeque; 12 | import java.util.Queue; 13 | 14 | import javax.annotation.Nullable; 15 | 16 | /** An {@link ActiveConnection} which buffers packets until one of the following: 17 | *
    18 | *
  1. {@link #flushQueue()} is called
  2. 19 | *
  3. {@link #maximumPacketSize()} is reached
  4. . 20 | *
  5. {@link #tick()} is called, which also calls {@link #sendTickPacket()} beforehand.
  6. 21 | *
22 | */ 23 | public abstract class BufferedConnection extends ActiveConnection { 24 | 25 | /** The minimum accepted value for {@link #ourMaxBandwidth} and {@link #theirMaxBandwidth}, in bytes per second. */ 26 | private static final int MIN_BANDWIDTH = 8000; 27 | 28 | // for testing purposes 29 | public static final boolean ENABLE_QUEUE = true; 30 | 31 | final int defaultDropDelay; 32 | private int ourMaxBandwidth = MIN_BANDWIDTH; 33 | private int theirMaxBandwidth = MIN_BANDWIDTH; 34 | private int actualMaxBandwidth = MIN_BANDWIDTH; 35 | 36 | private final Queue packetQueue = new ArrayDeque<>(); 37 | private int queueLength = 0; 38 | 39 | // private long lastTickTime; 40 | 41 | public BufferedConnection(ParentNetId rootId, int defaultDropDelay) { 42 | super(rootId); 43 | this.defaultDropDelay = defaultDropDelay; 44 | } 45 | 46 | public void setMaxBandwidth(int to) { 47 | if (to < MIN_BANDWIDTH) { 48 | to = MIN_BANDWIDTH; 49 | } 50 | ourMaxBandwidth = to; 51 | NetByteBuf data = NetByteBuf.buffer(6); 52 | data.writeVarUnsignedInt(InternalMsgUtil.ID_INTERNAL_NEW_BANDWIDTH); 53 | data.writeShort(to / MIN_BANDWIDTH); 54 | sendRawData0(data); 55 | data.release(); 56 | } 57 | 58 | @Override 59 | protected final void sendPacket(NetByteBuf data, int packetId, @Nullable NetIdBase netId, int priority) { 60 | if (!ENABLE_QUEUE) { 61 | sendRawData0(data); 62 | return; 63 | } 64 | int rb = data.readableBytes(); 65 | if (queueLength + rb > maximumPacketSize()) { 66 | flushQueue(); 67 | } 68 | if (rb > maximumPacketSize()) { 69 | // Sending a huge packet 70 | // Instead of splitting it ourselves we'll just make the implementation do it 71 | sendRawData0(data); 72 | } else { 73 | packetQueue.add(new BufferedPacketInfo(data, priority)); 74 | queueLength += rb; 75 | data.retain(); 76 | 77 | if (netId != null && (netId.getFinalFlags() & NetIdBase.FLAG_NOT_BUFFERED) != 0) { 78 | flushQueue(); 79 | } 80 | } 81 | } 82 | 83 | /** @return The maximum packet size that a single output packet can be. Used to try to keep the data sent to 84 | * {@link #sendRawData0(NetByteBuf)} below this value when combining data into packets. */ 85 | protected int maximumPacketSize() { 86 | // Default to a 65K, so that we don't end up with huge output packets. 87 | return (1 << 16) - 10; 88 | } 89 | 90 | /** Ticks this connection, flushing all queued data that needs to be sent immediately. */ 91 | public void tick() { 92 | // long thisTick = System.currentTimeMillis(); 93 | 94 | // FOR NOW 95 | // just empty the queue 96 | // rather than doing anything more complicated with bandwidth or priorities. 97 | // at the very least this should be an optimisation over just sending each individual 98 | // packet out one by one. 99 | sendTickPacket(); 100 | flushQueue(); 101 | } 102 | 103 | /** Optional method for subclasses to send additional packet before the queue is flushed. */ 104 | protected void sendTickPacket() {} 105 | 106 | @Override 107 | public void flushQueue() { 108 | if (!hasPackets()) { 109 | return; 110 | } 111 | if (packetQueue.size() == 1) { 112 | NetByteBuf data = packetQueue.remove().data; 113 | sendRawData0(data); 114 | data.release(); 115 | } else { 116 | NetByteBuf combined = NetByteBuf.buffer(queueLength); 117 | BufferedPacketInfo bpi; 118 | while ((bpi = packetQueue.poll()) != null) { 119 | combined.writeBytes(bpi.data); 120 | bpi.data.release(); 121 | } 122 | sendRawData0(combined); 123 | combined.release(); 124 | } 125 | queueLength = 0; 126 | } 127 | 128 | protected final boolean hasPackets() { 129 | return !packetQueue.isEmpty(); 130 | } 131 | 132 | @Override 133 | public void onReceiveRawData(NetByteBuf data) throws InvalidInputDataException { 134 | while (data.readableBytes() > 0) { 135 | InternalMsgUtil.onReceive(this, data); 136 | } 137 | } 138 | 139 | /** Sends some raw data. It might contain multiple packets, half packets, or even less. Either way the 140 | * implementation should just directly send the data on to the other side, and ensure it arrives in-order. */ 141 | protected abstract void sendRawData0(NetByteBuf data); 142 | 143 | void updateTheirMaxBandwidth(int theirs) { 144 | theirs *= MIN_BANDWIDTH; 145 | theirMaxBandwidth = Math.max(theirs, MIN_BANDWIDTH); 146 | actualMaxBandwidth = Math.min(theirMaxBandwidth, ourMaxBandwidth); 147 | } 148 | 149 | static class BufferedPacketInfo { 150 | final NetByteBuf data; 151 | final int priority; 152 | // final boolean delayable; 153 | 154 | public BufferedPacketInfo(NetByteBuf data, int priority) { 155 | this.data = data; 156 | this.priority = priority; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/DynamicNetId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.List; 11 | 12 | public final class DynamicNetId extends ParentNetIdSingle { 13 | 14 | @FunctionalInterface 15 | public interface LinkAccessor { 16 | DynamicNetLink getLink(T value); 17 | } 18 | 19 | final LinkAccessor linkGetter; 20 | 21 | public DynamicNetId(Class clazz, LinkAccessor linkGetter) { 22 | super(null, clazz, "dynamic", DYNAMIC_LENGTH); 23 | this.linkGetter = linkGetter; 24 | } 25 | 26 | @Override 27 | protected T readContext(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 28 | throw new IllegalStateException("Dynamic Net ID's must be fully resolved before they can be read!"); 29 | } 30 | 31 | @Override 32 | protected void writeContext(NetByteBuf buffer, IMsgWriteCtx ctx, T value) { 33 | throw new IllegalStateException("Dynamic Net ID's must be written with the dynamic variant!"); 34 | } 35 | 36 | @Override 37 | protected void writeDynamicContext( 38 | CheckingNetByteBuf buffer, IMsgWriteCtx ctx, T value, List resolvedPath 39 | ) { 40 | writeParent(buffer, ctx, linkGetter.getLink(value), resolvedPath); 41 | resolvedPath.add(this); 42 | } 43 | 44 | private

void writeParent( 45 | CheckingNetByteBuf buffer, IMsgWriteCtx ctx, DynamicNetLink link, List resolvedPath 46 | ) { 47 | assert link.parentId.childId == this; 48 | link.parentId.parent.writeDynamicContext(buffer, ctx, link.parent, resolvedPath); 49 | resolvedPath.add(link.parentId); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/DynamicNetLink.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | /** The container object 11 | * 12 | * @param

The parent type. 13 | * @param The child type. */ 14 | public final class DynamicNetLink { 15 | 16 | @FunctionalInterface 17 | public interface IDynamicLinkFactory { 18 | DynamicNetLink create(C child); 19 | } 20 | 21 | public final ParentDynamicNetId parentId; 22 | public final P parent; 23 | public final C child; 24 | 25 | public DynamicNetLink(ParentDynamicNetId parentId, P parent, C child) { 26 | this.parentId = parentId; 27 | this.parent = parent; 28 | this.child = child; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/EnumNetSide.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import alexiil.mc.lib.net.impl.ActiveMinecraftConnection; 11 | 12 | /** The side of an {@link ActiveMinecraftConnection}. This is in the API to make it a lot simpler to use. */ 13 | public enum EnumNetSide { 14 | SERVER, 15 | CLIENT; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/IMsgCtx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import net.minecraft.network.NetworkSide; 11 | 12 | import alexiil.mc.lib.net.impl.ActiveMinecraftConnection; 13 | 14 | public interface IMsgCtx { 15 | 16 | ActiveConnection getConnection(); 17 | 18 | /** @return The {@link NetworkSide} for this {@link #getConnection()}, assuming that it is an 19 | * {@link ActiveMinecraftConnection}. */ 20 | default EnumNetSide getNetSide() { 21 | ActiveConnection connection = getConnection(); 22 | return ((ActiveMinecraftConnection) connection).getNetSide(); 23 | } 24 | 25 | /** @return true if the code calling this is running as the master/server. (In normal connections the dedicated or 26 | * integrated server is the master, and the client is the slave). Assumes that the connection is an 27 | * {@link ActiveMinecraftConnection}. */ 28 | default boolean isServerSide() { 29 | return getNetSide() == EnumNetSide.SERVER; 30 | } 31 | 32 | /** @return true if the code calling this is running as the slave/client. (In normal connections the dedicated or 33 | * integrated server is the master, and the client is the slave). Assumes that the connection is an 34 | * {@link ActiveMinecraftConnection}. */ 35 | default boolean isClientSide() { 36 | return getNetSide() == EnumNetSide.CLIENT; 37 | } 38 | 39 | NetIdBase getNetId(); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/IMsgReadCtx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | public interface IMsgReadCtx extends IMsgCtx { 11 | 12 | default void assertClientSide() throws InvalidInputDataException { 13 | if (isServerSide()) { 14 | throw new InvalidInputDataException("Cannot read " + getNetId() + " on the server!"); 15 | } 16 | } 17 | 18 | default void assertServerSide() throws InvalidInputDataException { 19 | if (isClientSide()) { 20 | throw new InvalidInputDataException("Cannot read " + getNetId() + " on the client!"); 21 | } 22 | } 23 | 24 | /** Informs the supplier that this packet has been dropped (for any reason) and so there might be unread data left 25 | * in the buffer. */ 26 | default void drop() { 27 | drop(""); 28 | } 29 | 30 | /** Informs the supplier that this packet has been dropped (for the given reason) and so there might be unread data 31 | * left in the buffer. */ 32 | void drop(String reason); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/IMsgWriteCtx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | public interface IMsgWriteCtx extends IMsgCtx { 11 | 12 | default void assertClientSide() { 13 | if (isServerSide()) { 14 | throw new IllegalStateException("Cannot write " + getNetId() + " on the server!"); 15 | } 16 | } 17 | 18 | default void assertServerSide() { 19 | if (isClientSide()) { 20 | throw new IllegalStateException("Cannot write " + getNetId() + " on the client!"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/InvalidInputDataException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.io.IOException; 11 | 12 | public class InvalidInputDataException extends IOException { 13 | private static final long serialVersionUID = 1450919830268637665L; 14 | 15 | public InvalidInputDataException() {} 16 | 17 | public InvalidInputDataException(String message) { 18 | super(message); 19 | } 20 | 21 | public InvalidInputDataException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/LibNetworkStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.FileOutputStream; 13 | import java.io.IOException; 14 | import java.io.InputStreamReader; 15 | import java.io.OutputStreamWriter; 16 | import java.io.Writer; 17 | import java.nio.charset.StandardCharsets; 18 | import java.util.Properties; 19 | 20 | import org.apache.logging.log4j.LogManager; 21 | import org.apache.logging.log4j.Logger; 22 | 23 | import net.fabricmc.loader.api.FabricLoader; 24 | 25 | import net.fabricmc.api.ModInitializer; 26 | 27 | import alexiil.mc.lib.net.impl.CoreMinecraftNetUtil; 28 | 29 | public class LibNetworkStack implements ModInitializer { 30 | 31 | public static final String MODID = "libnetworkstack"; 32 | public static final boolean DEBUG; 33 | public static final Logger LOGGER = LogManager.getLogger("LibNetworkStack"); 34 | 35 | public static final String CONFIG_FILE_LOCATION; 36 | public static final boolean CONFIG_RECORD_TYPES; 37 | public static final boolean CONFIG_RECORD_STACKTRACES; 38 | 39 | static { 40 | boolean debug = Boolean.getBoolean("libnetworkstack.debug"); 41 | 42 | FabricLoader fabric = FabricLoader.getInstance(); 43 | final File cfgDir; 44 | if (fabric.getGameDir() == null) { 45 | // Can happen during a JUnit test 46 | cfgDir = new File("config"); 47 | } else { 48 | cfgDir = fabric.getConfigDirectory(); 49 | } 50 | if (!cfgDir.isDirectory()) { 51 | cfgDir.mkdirs(); 52 | } 53 | File cfgFile = new File(cfgDir, MODID + ".txt"); 54 | String fileLoc; 55 | try { 56 | fileLoc = cfgFile.getCanonicalPath(); 57 | } catch (IOException io) { 58 | fileLoc = cfgFile.getAbsolutePath(); 59 | LOGGER.warn("[config] Failed to get the canonical location of " + cfgFile, io); 60 | } 61 | CONFIG_FILE_LOCATION = fileLoc; 62 | Properties props = new Properties(); 63 | boolean didFileExist = cfgFile.exists(); 64 | if (didFileExist) { 65 | try (InputStreamReader isr = new InputStreamReader(new FileInputStream(cfgFile), StandardCharsets.UTF_8)) { 66 | props.load(isr); 67 | } catch (IOException e) { 68 | LOGGER.error("[config] Failed to read the config file!", e); 69 | } 70 | } 71 | 72 | boolean hasAll = true; 73 | 74 | boolean forceEnabled = FabricLoader.getInstance().isModLoaded("network-drain-cleaner"); 75 | 76 | hasAll &= props.containsKey("debug.log"); 77 | debug |= "true".equalsIgnoreCase(props.getProperty("debug.log", "false")); 78 | 79 | DEBUG = debug; 80 | 81 | hasAll &= props.containsKey("debug.record_types"); 82 | CONFIG_RECORD_TYPES = forceEnabled || "true".equalsIgnoreCase(props.getProperty("debug.record_types", "false")); 83 | 84 | hasAll &= props.containsKey("debug.record_stacktraces"); 85 | CONFIG_RECORD_STACKTRACES 86 | = forceEnabled || "true".equalsIgnoreCase(props.getProperty("debug.record_stacktraces", "false")); 87 | 88 | if (!hasAll) { 89 | try (Writer fw = new OutputStreamWriter(new FileOutputStream(cfgFile, true), StandardCharsets.UTF_8)) { 90 | if (!didFileExist) { 91 | fw.append("# LibNetworkStack configuration file.\n"); 92 | fw.append("# Removing an option will reset it back to the default value.\n"); 93 | fw.append("# Removing or altering comments doesn't replace them.\n\n"); 94 | } 95 | 96 | if (!props.containsKey("debug.log")) { 97 | fw.append("# True to enable all debug logging.\n"); 98 | fw.append("debug.log=false\n\n"); 99 | } 100 | 101 | if (!props.containsKey("debug.record_types")) { 102 | fw.append("# True to enable recording type information when writing packets\n"); 103 | fw.append("# (which is used when an exception is thrown while reading packets)\n"); 104 | fw.append("debug.record_types=false\n\n"); 105 | } 106 | 107 | if (!props.containsKey("debug.record_stacktraces")) { 108 | fw.append("# True to enable recording stacktraces when writing packets\n"); 109 | fw.append("# (which is used when an exception is thrown while reading packets)\n"); 110 | fw.append("# Unlike 'debug.record_types' this adds quite a lot of overhead,\n"); 111 | fw.append("# so should only be used when absolutely necessary.\n"); 112 | fw.append("debug.record_stacktraces=false\n\n"); 113 | } 114 | 115 | } catch (IOException e) { 116 | LOGGER.warn("[config] Failed to write the config file!", e); 117 | } 118 | } 119 | } 120 | 121 | @Override 122 | public void onInitialize() { 123 | CoreMinecraftNetUtil.load(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/LibNetworkStackClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import net.fabricmc.api.ClientModInitializer; 11 | 12 | import alexiil.mc.lib.net.impl.CoreMinecraftNetUtil; 13 | 14 | public class LibNetworkStackClient implements ClientModInitializer { 15 | @Override 16 | public void onInitializeClient() { 17 | CoreMinecraftNetUtil.loadClient(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/MessageContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import net.minecraft.block.entity.BlockEntity; 11 | 12 | public abstract class MessageContext implements IMsgCtx { 13 | 14 | public final ActiveConnection connection; 15 | public final NetIdBase id; 16 | 17 | public MessageContext(ActiveConnection connection, NetIdBase id) { 18 | this.connection = connection; 19 | this.id = id; 20 | } 21 | 22 | @Override 23 | public ActiveConnection getConnection() { 24 | return connection; 25 | } 26 | 27 | @Override 28 | public NetIdBase getNetId() { 29 | return id; 30 | } 31 | 32 | public static class Read extends MessageContext implements IMsgReadCtx { 33 | 34 | /** A non-null value indicates that the read message was dropped. */ 35 | public String dropReason; 36 | 37 | public Read(ActiveConnection connection, NetIdBase id) { 38 | super(connection, id); 39 | } 40 | 41 | @Override 42 | public void drop(String reason) { 43 | if (reason == null) { 44 | reason = ""; 45 | } 46 | dropReason = reason; 47 | } 48 | } 49 | 50 | public static class Write extends MessageContext implements IMsgWriteCtx { 51 | 52 | /** @deprecated WARNING: Temporary field, used solely to debug an issue. DO NOT TOUCH FROM THIS FIELD IF YOU 53 | * AREN'T LIB NETWORK STACK! */ 54 | @Deprecated 55 | public BlockEntity __debugBlockEntity; 56 | 57 | public Write(ActiveConnection connection, NetIdBase id) { 58 | super(connection, id); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/MsgUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.nio.charset.StandardCharsets; 11 | 12 | import io.netty.buffer.ByteBuf; 13 | import io.netty.buffer.Unpooled; 14 | import io.netty.util.internal.StringUtil; 15 | 16 | /** Various utilities for reading and writing data */ 17 | public final class MsgUtil { 18 | private MsgUtil() {} 19 | 20 | public static void writeUTF(NetByteBuf buffer, String string) { 21 | byte[] data = string.getBytes(StandardCharsets.UTF_8); 22 | if (data.length > 0xFF_FF) { 23 | throw new IllegalArgumentException("Cannot write a string with more than " + 0xFF_FF + " bytes!"); 24 | } 25 | buffer.writeShort(data.length); 26 | buffer.writeBytes(data); 27 | } 28 | 29 | public static String readUTF(NetByteBuf buffer) { 30 | int len = buffer.readUnsignedShort(); 31 | byte[] data = new byte[len]; 32 | buffer.readBytes(data); 33 | return new String(data, StandardCharsets.UTF_8); 34 | } 35 | 36 | /** Checks to make sure that this buffer has been *completely* read (so that there are no readable bytes left 37 | * over */ 38 | public static void ensureEmpty(ByteBuf buf, boolean throwError, String extra) throws InvalidInputDataException { 39 | int readableBytes = buf.readableBytes(); 40 | int rb = readableBytes; 41 | 42 | if (buf instanceof NetByteBuf) { 43 | // TODO: Find a way of checking if the partial bits have been fully read! 44 | } 45 | 46 | if (readableBytes > 0) { 47 | int ri = buf.readerIndex(); 48 | ByteBuf dst = Unpooled.buffer(); 49 | buf.getBytes(0, dst, buf.writerIndex()); 50 | String summary = formatPacketSelection(dst, dst.readableBytes(), ri); 51 | IllegalStateException ex 52 | = new IllegalStateException("Did not fully read the data! [" + extra + "]\n" + summary + " --" + rb); 53 | if (throwError) { 54 | throw ex; 55 | } else { 56 | LibNetworkStack.LOGGER.warn(ex); 57 | } 58 | buf.clear(); 59 | } 60 | } 61 | 62 | public static void printWholeBuffer(ByteBuf buf) { 63 | int readerIndex = buf.readerIndex(); 64 | ByteBuf dst = Unpooled.buffer(); 65 | buf.getBytes(0, dst, buf.writerIndex()); 66 | LibNetworkStack.LOGGER.info(" " + formatPacketSelection(dst, dst.readableBytes(), readerIndex)); 67 | } 68 | 69 | private static String formatPacketSelection(ByteBuf buffer, int length, int readerIndex) { 70 | StringBuilder sb = new StringBuilder(); 71 | appendBufferData(buffer, length, sb, "", readerIndex); 72 | return sb.toString(); 73 | } 74 | 75 | public static void appendBufferData( 76 | ByteBuf buffer, int length, StringBuilder sb, String linePrefix, int readerIndex 77 | ) { 78 | appendBufferData(buffer, 0, length, sb, linePrefix, readerIndex); 79 | } 80 | 81 | public static void appendBufferData( 82 | ByteBuf buffer, int offset, int length, StringBuilder sb, String linePrefix, int readerIndex 83 | ) { 84 | linePrefix = "\n" + linePrefix; 85 | 86 | for (int i = 0; true; i++) { 87 | int from = i * 20; 88 | int to = Math.min(from + 20, length); 89 | if (from >= to) break; 90 | if (i > 0) { 91 | sb.append(linePrefix); 92 | } 93 | for (int j = from; j < to; j++) { 94 | byte b = buffer.getByte(j + offset); 95 | sb.append(StringUtil.byteToHexStringPadded(b)); 96 | if (j + 1 == readerIndex) { 97 | sb.append('#'); 98 | } else { 99 | sb.append(' '); 100 | } 101 | } 102 | int leftOver = from - to + 20; 103 | for (int j = 0; j < leftOver; j++) { 104 | sb.append(" "); 105 | } 106 | 107 | sb.append("| "); 108 | for (int j = from; j < to; j++) { 109 | byte b = buffer.getByte(j + offset); 110 | char c = (char) b; 111 | if (c < 32 || c > 127) { 112 | c = ' '; 113 | } 114 | sb.append(c); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetIdBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import alexiil.mc.lib.net.impl.ActiveMinecraftConnection; 11 | 12 | /** A leaf node that will send and receive messages. */ 13 | public abstract class NetIdBase extends TreeNetIdBase { 14 | 15 | /** The bit positions assigned to the sizes - {@link #FLAG_FIXED_SIZE}, {@link #FLAG_TINY_PACKET}, 16 | * {@link #FLAG_NORMAL_PACKET}, and {@link #FLAG_LARGE_PACKET}. */ 17 | static final int PACKET_SIZE_FLAG = 0b11; 18 | 19 | /** Pre-written packet length. */ 20 | static final int FLAG_FIXED_SIZE = 0b00; 21 | 22 | /** 1 byte for the packet length. */ 23 | static final int FLAG_TINY_PACKET = 0b01; 24 | 25 | /** 2 bytes for the packet length. */ 26 | static final int FLAG_NORMAL_PACKET = 0b10; 27 | 28 | /** 3 bytes for the packet length. */ 29 | static final int FLAG_LARGE_PACKET = 0b11; 30 | 31 | /** If present then this packet will not be buffered. */ 32 | static final int FLAG_NOT_BUFFERED = 0b1000; 33 | 34 | /** The bit positions assigned to the sides - {@link #FLAG_SIDED_RECV_ON_CLIENT} and 35 | * {@link #FLAG_SIDED_RECV_ON_SERVER}. */ 36 | static final int FLAGS_SIDED = 0b11_0000; 37 | 38 | /** If present then this packet may only be sent from the server to the client. */ 39 | static final int FLAG_SIDED_RECV_ON_CLIENT = 0b01_0000; 40 | 41 | /** If present then this packet may only be sent from the client to the server. */ 42 | static final int FLAG_SIDED_RECV_ON_SERVER = 0b10_0000; 43 | 44 | /** This priority indicates that this should never be dropped on the sending side. This is the equivalent priority 45 | * to minecraft's own packets. Generally this should be used for user interaction or other very important packets 46 | * that must not be delayed or dropped. */ 47 | public static final int MAXIMUM_PRIORITY = 0; 48 | 49 | // Internal flags - these don't apply to normal packets, but InternalMsgUtil uses them 50 | 51 | /** Used to determine if this is a parent node or a NetIdBase. By definition this cannot be present in this 52 | * class. */ 53 | static final int FLAG_IS_PARENT = 0b100; 54 | 55 | public static final int DEFAULT_FLAGS = // 56 | FLAG_NORMAL_PACKET// 57 | ; 58 | 59 | private int flags = hasFixedLength() ? FLAG_FIXED_SIZE : DEFAULT_FLAGS; 60 | private boolean flagsUsed = false; 61 | private int defaultPriority = MAXIMUM_PRIORITY; 62 | 63 | /** @see #setMaximumDropDelay(int) */ 64 | private int maximumDropDelay = 0; 65 | 66 | NetIdBase(ParentNetIdBase parent, String name, int length) { 67 | super(parent, name, length); 68 | } 69 | 70 | /** Changes the size flags of this net ID to indicate that it should use a single byte for the packet's total length 71 | * (including the header). In other words the maximum packet length is 256 bytes, and the minimum is 1 byte. Has no 72 | * effect if the packet's length is not {@link #hasFixedLength() fixed}. 73 | *

74 | * Generally you should call {@link #withTinySize()} rather than this, as it returns itself rather than nothing. 75 | * 76 | * @see #setNormalSize() 77 | * @see #setLargeSize() */ 78 | public final void setTinySize() { 79 | if (!hasFixedLength()) { 80 | changeFlag(flags & ~PACKET_SIZE_FLAG | FLAG_TINY_PACKET); 81 | } 82 | } 83 | 84 | /** Changes the size flags of this net ID to indicate that it should use a single byte for the packet's total length 85 | * (including the header). In other words the maximum packet length is 256 bytes, and the minimum is 1 byte. Has no 86 | * effect if the packet's length is not {@link #hasFixedLength() fixed}. 87 | *

88 | * This method should be preferred over {@link #setTinySize()} because it returns itself. 89 | * 90 | * @see #withNormalSize() 91 | * @see #withLargeSize() */ 92 | public abstract NetIdBase withTinySize(); 93 | 94 | /** Changes the size flags of this net ID to indicate that it should use two bytes for the packet's total length 95 | * (including the header). In other words the maximum packet length is 65,536 bytes, and the minimum is 1 byte. Has 96 | * no effect if the packet's length is not {@link #hasFixedLength() fixed}. 97 | *

98 | * Generally you should call {@link #withNormalSize()} rather than this, as it returns itself rather than nothing. 99 | * 100 | * @see #setTinySize() 101 | * @see #setLargeSize() */ 102 | public final void setNormalSize() { 103 | if (!hasFixedLength()) { 104 | changeFlag(flags & ~PACKET_SIZE_FLAG | FLAG_NORMAL_PACKET); 105 | } 106 | } 107 | 108 | /** Changes the size flags of this net ID to indicate that it should use two bytes for the packet's total length 109 | * (including the header). In other words the maximum packet length is 65,536 bytes, and the minimum is 1 byte. Has 110 | * no effect if the packet's length is not {@link #hasFixedLength() fixed}. 111 | *

112 | * This method should be preferred over {@link #setNormalSize()} because it returns itself. 113 | * 114 | * @see #withTinySize() 115 | * @see #withLargeSize() */ 116 | public abstract NetIdBase withNormalSize(); 117 | 118 | /** Changes the size flags of this net ID to indicate that it should use three bytes for the packet's total length 119 | * (including the header). In other words the maximum packet length is 16,777,216 bytes, and the minimum is 1 byte. 120 | * Has no effect if the packet's length is not {@link #hasFixedLength() fixed}. 121 | *

122 | * Generally you should call {@link #withLargeSize()} rather than this, as it returns itself rather than nothing. 123 | * 124 | * @see #setTinySize() 125 | * @see #setNormalSize() */ 126 | public final void setLargeSize() { 127 | if (!hasFixedLength()) { 128 | changeFlag(flags & ~PACKET_SIZE_FLAG | FLAG_LARGE_PACKET); 129 | } 130 | } 131 | 132 | /** Changes the size flags of this net ID to indicate that it should use three bytes for the packet's total length 133 | * (including the header). In other words the maximum packet length is 16,777,216 bytes, and the minimum is 1 byte. 134 | * Has no effect if the packet's length is not {@link #hasFixedLength() fixed}. 135 | *

136 | * Unlike all of the other flag modification methods this may be called at any time, in particular before and after 137 | * sending this. 138 | *

139 | * This method should be preferred over {@link #setLargeSize()} because it returns itself. 140 | * 141 | * @see #withTinySize() 142 | * @see #withNormalSize() */ 143 | public abstract NetIdBase withLargeSize(); 144 | 145 | /** Changes the flags for this packet to indicate that it should never be buffered by a {@link BufferedConnection}. 146 | * This means that it will case {@link ActiveConnection#flushQueue()} to be called after this is sent. Please note 147 | * that this does keep packet ordering - any previously written packets (which were buffered) will be read 148 | * before this one is read. Sending a lot of these "queue-flushing packets" will generally have a large impact on 149 | * performance. 150 | *

151 | * Unlike all of the other flag modification methods this may be called at any time, in particular before and after 152 | * sending this. 153 | *

154 | * Generally you should call {@link #withoutBuffering()} rather than this, as it returns itself rather than 155 | * nothing. */ 156 | public final void notBuffered() { 157 | flags |= FLAG_NOT_BUFFERED; 158 | } 159 | 160 | /** The inverse of {@link #notBuffered()}. (This is the default state). 161 | *

162 | * Unlike all of the other flag modification methods this may be called at any time, in particular before and after 163 | * sending this. */ 164 | public final void buffered() { 165 | flags &= ~FLAG_NOT_BUFFERED; 166 | } 167 | 168 | /** @param isBuffered If true then this calls {@link #buffered()}, otherwise this calls {@link #notBuffered()}. */ 169 | public final void setBuffered(boolean isBuffered) { 170 | if (isBuffered) { 171 | buffered(); 172 | } else { 173 | notBuffered(); 174 | } 175 | } 176 | 177 | /** Changes the flags for this packet to indicate that it should never be buffered by a {@link BufferedConnection}. 178 | * This means that it will case {@link ActiveConnection#flushQueue()} to be called after this is sent. Please note 179 | * that this does keep packet ordering - any previously written packets (which were buffered) will be read 180 | * before this one is read. Sending a lot of these "queue-flushing packets" will generally have a large impact on 181 | * performance. 182 | *

183 | * Unlike all of the other flag modification methods this may be called at any time, in particular before and after 184 | * sending this. 185 | *

186 | * This method should be preferred over {@link #notBuffered()} because it returns itself. 187 | * 188 | * @return This. */ 189 | public abstract NetIdBase withoutBuffering(); 190 | 191 | /** Impl for {@link #toClientOnly()} */ 192 | protected final void _toClientOnly() { 193 | changeFlag(flags & ~FLAGS_SIDED | FLAG_SIDED_RECV_ON_CLIENT); 194 | } 195 | 196 | /** Impl for {@link #toServerOnly()} */ 197 | protected final void _toServerOnly() { 198 | changeFlag(flags & ~FLAGS_SIDED | FLAG_SIDED_RECV_ON_SERVER); 199 | } 200 | 201 | /** Impl for {@link #toEitherSide()} */ 202 | protected final void _toEitherSide() { 203 | changeFlag(flags & ~FLAGS_SIDED); 204 | } 205 | 206 | /** Changes the flags for this packet to indicate that it should only be sent from the server to the client. This 207 | * will make sending this packet from the client throw an unchecked exception, and make the server refuse to bind 208 | * this packet to an integer ID for receiving. 209 | * 210 | * @see #toServerOnly() */ 211 | public abstract NetIdBase toClientOnly(); 212 | 213 | /** Changes the flags for this packet to indicate that it should only be sent from the client to the server. This 214 | * will make sending this packet from the server throw an unchecked exception, and make the client refuse to bind 215 | * this packet to an integer ID for receiving. 216 | * 217 | * @see #toClientOnly() */ 218 | public abstract NetIdBase toServerOnly(); 219 | 220 | /** This clears the {@link #toClientOnly()} and {@link #toServerOnly()} flag states, and will make sending packets 221 | * not throw exceptions. 222 | *

223 | * This is the default state for every {@link NetIdBase}. */ 224 | public abstract NetIdBase toEitherSide(); 225 | 226 | /** Sets the maximum time that this packet may be held before dropping it. This value is only used if the connection 227 | * tries to send too much data in a single tick. Negative values are not allowed. This indicates an absolute number 228 | * of connection ticks (for normal minecraft connections this is every server or client tick). */ 229 | public void setMaximumDropDelay(int dropDelay) { 230 | this.maximumDropDelay = Math.min(0, dropDelay); 231 | } 232 | 233 | protected final void changeFlag(int newFlags) { 234 | if (flagsUsed) { 235 | throw new IllegalStateException("You cannot modify the flags of this NetId as it has already been used!"); 236 | } 237 | this.flags = newFlags; 238 | } 239 | 240 | @Override 241 | final int getFinalFlags() { 242 | flagsUsed = true; 243 | return flags; 244 | } 245 | 246 | int getDefaultPriority() { 247 | return defaultPriority; 248 | } 249 | 250 | int getMaximumDropDelay() { 251 | return maximumDropDelay; 252 | } 253 | 254 | final void validateSendingSide(IMsgWriteCtx ctx) { 255 | validateSendingSide(ctx.getConnection()); 256 | } 257 | 258 | final void validateSendingSide(ActiveConnection connection) { 259 | int single = flags & FLAGS_SIDED; 260 | if (single == 0) { 261 | return; 262 | } 263 | if (!(connection instanceof ActiveMinecraftConnection)) { 264 | throw new IllegalStateException("You can only send " + this + " through a minecraft connection! (Was given " + connection + ")"); 265 | } 266 | ActiveMinecraftConnection a = (ActiveMinecraftConnection) connection; 267 | if (single == FLAG_SIDED_RECV_ON_CLIENT) { 268 | if (a.getNetSide() == EnumNetSide.CLIENT) { 269 | throw new IllegalStateException("Cannot write " + this + " on the client!"); 270 | } 271 | } else if (single == FLAG_SIDED_RECV_ON_SERVER) { 272 | if (a.getNetSide() == EnumNetSide.SERVER) { 273 | throw new IllegalStateException("Cannot write " + this + " on the server!"); 274 | } 275 | } 276 | } 277 | 278 | /** @return True if the parent element could be found, false if it's null. */ 279 | abstract boolean receive(CheckingNetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException; 280 | } 281 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetIdData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | public final class NetIdData extends NetIdSeparate { 11 | 12 | @FunctionalInterface 13 | public interface IMsgDataReceiver { 14 | void receive(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException; 15 | } 16 | 17 | @FunctionalInterface 18 | public interface IMsgDataWriter { 19 | void write(NetByteBuf buffer, IMsgWriteCtx ctx); 20 | } 21 | 22 | private IMsgDataReceiver receiver = (buffer, ctx) -> { 23 | throw new InvalidInputDataException("No receiver set!"); 24 | }; 25 | private IMsgDataWriter writer; 26 | 27 | public NetIdData(ParentNetId parent, String name, int length) { 28 | super(parent, name, length); 29 | } 30 | 31 | public NetIdData setReceiver(IMsgDataReceiver receiver) { 32 | this.receiver = receiver; 33 | return this; 34 | } 35 | 36 | public NetIdData setReadWrite(IMsgDataReceiver receiver, IMsgDataWriter writer) { 37 | this.receiver = receiver; 38 | this.writer = writer; 39 | return this; 40 | } 41 | 42 | @Override 43 | boolean receive(CheckingNetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 44 | receiver.receive(buffer, ctx); 45 | return true; 46 | } 47 | 48 | /** Sends this signal over the specified connection */ 49 | public void send(ActiveConnection connection) { 50 | send(connection, writer); 51 | } 52 | 53 | public void send(ActiveConnection connection, IMsgDataWriter writer) { 54 | MessageContext.Write ctx = new MessageContext.Write(connection, this); 55 | validateSendingSide(ctx); 56 | NetByteBuf buffer = hasFixedLength() ? connection.allocBuffer(totalLength) : connection.allocBuffer(); 57 | CheckingNetByteBuf checkingBuffer = null; 58 | if (connection.sendTypes) { 59 | checkingBuffer = new CheckingNetByteBuf(buffer, NetByteBuf.buffer()); 60 | } 61 | writer.write(checkingBuffer == null ? buffer : checkingBuffer, ctx); 62 | if (buffer.readableBytes() > 0) { 63 | // Only send data packets if anything was actually written. 64 | if (checkingBuffer != null) { 65 | InternalMsgUtil.sendNextTypes(connection, checkingBuffer.typeBuffer, checkingBuffer.getCountWrite()); 66 | } 67 | if (LibNetworkStack.CONFIG_RECORD_STACKTRACES && connection.sendStacktraces) { 68 | InternalMsgUtil.sendNextStacktrace(connection, new Throwable().fillInStackTrace()); 69 | } 70 | InternalMsgUtil.send(connection, this, path, buffer); 71 | } 72 | buffer.release(); 73 | } 74 | 75 | @Override 76 | public NetIdData withoutBuffering() { 77 | notBuffered(); 78 | return this; 79 | } 80 | 81 | @Override 82 | public NetIdData withTinySize() { 83 | setTinySize(); 84 | return this; 85 | } 86 | 87 | @Override 88 | public NetIdData withNormalSize() { 89 | setNormalSize(); 90 | return this; 91 | } 92 | 93 | @Override 94 | public NetIdData withLargeSize() { 95 | setLargeSize(); 96 | return this; 97 | } 98 | 99 | @Override 100 | public NetIdData toClientOnly() { 101 | _toClientOnly(); 102 | return this; 103 | } 104 | 105 | @Override 106 | public NetIdData toServerOnly() { 107 | _toServerOnly(); 108 | return this; 109 | } 110 | 111 | @Override 112 | public NetIdData toEitherSide() { 113 | _toEitherSide(); 114 | return this; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetIdDataK.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public final class NetIdDataK extends NetIdTyped { 14 | 15 | @FunctionalInterface 16 | public interface IMsgDataReceiverK { 17 | void receive(T obj, NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException; 18 | } 19 | 20 | @FunctionalInterface 21 | public interface IMsgDataWriterK { 22 | void write(T obj, NetByteBuf buffer, IMsgWriteCtx ctx); 23 | } 24 | 25 | private IMsgDataReceiverK receiver = (t, buffer, ctx) -> { 26 | throw new InvalidInputDataException("No receiver set for " + ctx.getNetSide()); 27 | }; 28 | private IMsgDataWriterK writer; 29 | 30 | public NetIdDataK(ParentNetIdSingle parent, String name, int length) { 31 | super(parent, name, length); 32 | } 33 | 34 | public NetIdDataK setReceiver(IMsgDataReceiverK receiver) { 35 | this.receiver = receiver; 36 | return this; 37 | } 38 | 39 | public NetIdDataK setReadWrite(IMsgDataReceiverK receiver, IMsgDataWriterK writer) { 40 | this.receiver = receiver; 41 | this.writer = writer; 42 | return this; 43 | } 44 | 45 | @Override 46 | public void receive(NetByteBuf buffer, IMsgReadCtx ctx, T parentValue) throws InvalidInputDataException { 47 | receiver.receive(parentValue, buffer, ctx); 48 | } 49 | 50 | /** Sends this signal over the specified connection */ 51 | @Override 52 | public void send(ActiveConnection connection, T obj) { 53 | send(connection, obj, writer); 54 | } 55 | 56 | public void send(ActiveConnection connection, T obj, IMsgDataWriterK writer) { 57 | MessageContext.Write ctx = new MessageContext.Write(connection, this); 58 | validateSendingSide(ctx); 59 | NetByteBuf buffer = hasFixedLength() ? connection.allocBuffer(totalLength) : connection.allocBuffer(); 60 | NetByteBuf bufferTypes = connection.sendTypes ? NetByteBuf.buffer() : null; 61 | CheckingNetByteBuf checkingBuffer = new CheckingNetByteBuf(buffer, bufferTypes); 62 | final NetIdPath resolvedPath; 63 | if (parent.pathContainsDynamicParent) { 64 | List nPath = new ArrayList<>(); 65 | parent.writeDynamicContext(checkingBuffer, ctx, obj, nPath); 66 | nPath.add(this); 67 | resolvedPath = new NetIdPath(nPath); 68 | } else { 69 | parent.writeContextCall(checkingBuffer, ctx, obj); 70 | resolvedPath = this.path; 71 | } 72 | if (checkingBuffer.hasTypeData()) { 73 | int thisId = InternalMsgUtil.getWriteId(connection, this, resolvedPath); 74 | checkingBuffer.writeMarkerId(thisId); 75 | } 76 | int headerLength = checkingBuffer.writerIndex(); 77 | int headerBitLength = checkingBuffer.getBitWriterIndex(); 78 | writer.write(obj, checkingBuffer, ctx); 79 | if (headerLength != checkingBuffer.writerIndex() || headerBitLength != checkingBuffer.getBitWriterIndex()) { 80 | // Only send data packets if anything was actually written. 81 | if (bufferTypes != null) { 82 | InternalMsgUtil.sendNextTypes(connection, bufferTypes, checkingBuffer.getCountWrite()); 83 | } 84 | if (LibNetworkStack.CONFIG_RECORD_STACKTRACES && connection.sendStacktraces) { 85 | InternalMsgUtil.createAndSendDebugThrowable(connection, ctx); 86 | } 87 | InternalMsgUtil.send(connection, this, resolvedPath, buffer); 88 | } 89 | buffer.release(); 90 | } 91 | 92 | @Override 93 | public NetIdDataK withoutBuffering() { 94 | notBuffered(); 95 | return this; 96 | } 97 | 98 | @Override 99 | public NetIdDataK withTinySize() { 100 | setTinySize(); 101 | return this; 102 | } 103 | 104 | @Override 105 | public NetIdDataK withNormalSize() { 106 | setNormalSize(); 107 | return this; 108 | } 109 | 110 | @Override 111 | public NetIdDataK withLargeSize() { 112 | setLargeSize(); 113 | return this; 114 | } 115 | 116 | @Override 117 | public NetIdDataK toClientOnly() { 118 | _toClientOnly(); 119 | return this; 120 | } 121 | 122 | @Override 123 | public NetIdDataK toServerOnly() { 124 | _toServerOnly(); 125 | return this; 126 | } 127 | 128 | @Override 129 | public NetIdDataK toEitherSide() { 130 | _toEitherSide(); 131 | return this; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetIdPath.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | final class NetIdPath { 14 | private static final NetIdPath EMPTY = new NetIdPath(new TreeNetIdBase[0]); 15 | 16 | final TreeNetIdBase[] array; 17 | final int hash; 18 | 19 | NetIdPath(TreeNetIdBase[] array) { 20 | this.array = array; 21 | this.hash = Arrays.hashCode(array); 22 | } 23 | 24 | NetIdPath(TreeNetIdBase single) { 25 | this(new TreeNetIdBase[] { single }); 26 | } 27 | 28 | NetIdPath(List list) { 29 | this(list.toArray(new TreeNetIdBase[0])); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | if (array.length == 0) { 35 | return ""; 36 | } 37 | StringBuilder sb = new StringBuilder(); 38 | sb.append(array[0].name); 39 | for (int i = 1; i < array.length; i++) { 40 | TreeNetIdBase id = array[i]; 41 | sb.append("."); 42 | sb.append(id.name); 43 | } 44 | return sb.toString(); 45 | } 46 | 47 | NetIdPath parent() { 48 | if (array.length <= 1) { 49 | return EMPTY; 50 | } 51 | return new NetIdPath(Arrays.copyOfRange(array, 0, array.length - 1)); 52 | } 53 | 54 | NetIdPath withChild(TreeNetIdBase child) { 55 | TreeNetIdBase[] arr = Arrays.copyOf(array, array.length + 1); 56 | arr[array.length] = child; 57 | return new NetIdPath(arr); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return hash; 63 | } 64 | 65 | @Override 66 | public boolean equals(Object obj) { 67 | if (obj == this) return true; 68 | if (obj == null) return false; 69 | if (obj instanceof NetIdPath) { 70 | NetIdPath other = (NetIdPath) obj; 71 | if (hash != other.hash || array.length != other.array.length) { 72 | return false; 73 | } 74 | // Compare the last element first (the child) 75 | // as that's the one that is most likely to be different. 76 | for (int i = array.length - 1; i >= 0; i--) { 77 | // Use identity comparison as everything works that way. 78 | if (array[i] != other.array[i]) { 79 | return false; 80 | } 81 | } 82 | return true; 83 | } 84 | return false; 85 | } 86 | 87 | int calculateLength() { 88 | int len = 0; 89 | for (TreeNetIdBase id : array) { 90 | if (id.length == TreeNetIdBase.DYNAMIC_LENGTH) { 91 | return TreeNetIdBase.DYNAMIC_LENGTH; 92 | } 93 | len += id.length; 94 | } 95 | return len; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetIdSeparate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | /** Base class for {@link NetIdData} and {@link NetIdSignal}. */ 11 | public abstract class NetIdSeparate extends NetIdBase { 12 | NetIdSeparate(ParentNetId parent, String name, int length) { 13 | super(parent, name, length); 14 | parent.addChild(this); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetIdSignal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | public final class NetIdSignal extends NetIdSeparate { 11 | 12 | @FunctionalInterface 13 | public interface IMsgSignalReceiver { 14 | void handle(IMsgReadCtx ctx) throws InvalidInputDataException; 15 | } 16 | 17 | private IMsgSignalReceiver receiver; 18 | 19 | public NetIdSignal(ParentNetId parent, String name) { 20 | super(parent, name, 0); 21 | } 22 | 23 | public NetIdSignal setReceiver(IMsgSignalReceiver receiver) { 24 | this.receiver = receiver; 25 | return this; 26 | } 27 | 28 | /** Sends this signal over the specified connection */ 29 | public void send(ActiveConnection connection) { 30 | validateSendingSide(connection); 31 | if (LibNetworkStack.CONFIG_RECORD_STACKTRACES && connection.sendStacktraces) { 32 | InternalMsgUtil.sendNextStacktrace(connection, new Throwable().fillInStackTrace()); 33 | } 34 | InternalMsgUtil.send(connection, this, path, NetByteBuf.EMPTY_BUFFER); 35 | } 36 | 37 | // Internals 38 | 39 | @Override 40 | boolean receive(CheckingNetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 41 | if (receiver != null) { 42 | receiver.handle(ctx); 43 | } 44 | return true; 45 | } 46 | 47 | @Override 48 | public NetIdSignal withoutBuffering() { 49 | notBuffered(); 50 | return this; 51 | } 52 | 53 | @Override 54 | public NetIdSignal withTinySize() { 55 | setTinySize(); 56 | return this; 57 | } 58 | 59 | @Override 60 | public NetIdSignal withNormalSize() { 61 | setNormalSize(); 62 | return this; 63 | } 64 | 65 | @Override 66 | public NetIdSignal withLargeSize() { 67 | setLargeSize(); 68 | return this; 69 | } 70 | 71 | @Override 72 | public NetIdSignal toClientOnly() { 73 | _toClientOnly(); 74 | return this; 75 | } 76 | 77 | @Override 78 | public NetIdSignal toServerOnly() { 79 | _toServerOnly(); 80 | return this; 81 | } 82 | 83 | @Override 84 | public NetIdSignal toEitherSide() { 85 | _toEitherSide(); 86 | return this; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetIdSignalK.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public final class NetIdSignalK extends NetIdTyped { 14 | 15 | @FunctionalInterface 16 | public interface IMsgSignalReceiverK { 17 | void handle(T obj, IMsgReadCtx ctx) throws InvalidInputDataException; 18 | } 19 | 20 | private IMsgSignalReceiverK receiver; 21 | 22 | public NetIdSignalK(ParentNetIdSingle parent, String name) { 23 | super(parent, name, 0); 24 | } 25 | 26 | public NetIdSignalK setReceiver(IMsgSignalReceiverK receiver) { 27 | this.receiver = receiver; 28 | return this; 29 | } 30 | 31 | @Override 32 | protected void receive(NetByteBuf buffer, IMsgReadCtx ctx, T obj) throws InvalidInputDataException { 33 | if (receiver != null) { 34 | receiver.handle(obj, ctx); 35 | } 36 | } 37 | 38 | @Override 39 | public NetIdSignalK withoutBuffering() { 40 | notBuffered(); 41 | return this; 42 | } 43 | 44 | @Override 45 | public NetIdSignalK withTinySize() { 46 | setTinySize(); 47 | return this; 48 | } 49 | 50 | @Override 51 | public NetIdSignalK withNormalSize() { 52 | setNormalSize(); 53 | return this; 54 | } 55 | 56 | @Override 57 | public NetIdSignalK withLargeSize() { 58 | setLargeSize(); 59 | return this; 60 | } 61 | 62 | @Override 63 | public NetIdSignalK toClientOnly() { 64 | _toClientOnly(); 65 | return this; 66 | } 67 | 68 | @Override 69 | public NetIdSignalK toServerOnly() { 70 | _toServerOnly(); 71 | return this; 72 | } 73 | 74 | @Override 75 | public NetIdSignalK toEitherSide() { 76 | _toEitherSide(); 77 | return this; 78 | } 79 | 80 | /** Sends this signal over the specified connection */ 81 | @Override 82 | public void send(ActiveConnection connection, T obj) { 83 | MessageContext.Write ctx = new MessageContext.Write(connection, this); 84 | validateSendingSide(ctx); 85 | NetByteBuf buffer = hasFixedLength() ? NetByteBuf.buffer(totalLength) : NetByteBuf.buffer(); 86 | NetByteBuf bufferTypes = connection.sendTypes ? NetByteBuf.buffer() : null; 87 | CheckingNetByteBuf checkingBuffer = new CheckingNetByteBuf(buffer, bufferTypes); 88 | NetIdPath resolvedPath; 89 | if (parent.pathContainsDynamicParent) { 90 | List nPath = new ArrayList<>(); 91 | parent.writeDynamicContext(checkingBuffer, ctx, obj, nPath); 92 | nPath.add(this); 93 | resolvedPath = new NetIdPath(nPath); 94 | } else { 95 | parent.writeContextCall(checkingBuffer, ctx, obj); 96 | resolvedPath = path; 97 | } 98 | if (checkingBuffer.hasTypeData()) { 99 | int thisId = InternalMsgUtil.getWriteId(connection, this, resolvedPath); 100 | checkingBuffer.writeMarkerId(thisId); 101 | } 102 | if (bufferTypes != null) { 103 | InternalMsgUtil.sendNextTypes(connection, bufferTypes, checkingBuffer.getCountWrite()); 104 | } 105 | if (LibNetworkStack.CONFIG_RECORD_STACKTRACES && connection.sendStacktraces) { 106 | InternalMsgUtil.createAndSendDebugThrowable(connection, ctx); 107 | } 108 | InternalMsgUtil.send(connection, this, resolvedPath, buffer); 109 | buffer.release(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetIdTyped.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | public abstract class NetIdTyped extends NetIdBase { 11 | 12 | public final ParentNetIdSingle parent; 13 | 14 | NetIdTyped(ParentNetIdSingle parent, String name, int length) { 15 | super(parent, name, length); 16 | this.parent = parent; 17 | if (!(this instanceof ResolvedNetId)) { 18 | parent.addChild(this); 19 | } 20 | } 21 | 22 | @Override 23 | protected String getPrintableName() { 24 | return getRealClassName() + " <" + parent.clazz.getSimpleName() + ">"; 25 | } 26 | 27 | @Override 28 | final boolean receive(CheckingNetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 29 | T obj = parent.readContextCall(buffer, ctx); 30 | if (obj == null) { 31 | return false; 32 | } else { 33 | buffer.readMarkerId(ctx, this); 34 | receive(buffer, ctx, obj); 35 | return true; 36 | } 37 | } 38 | 39 | protected abstract void receive(NetByteBuf buffer, IMsgReadCtx ctx, T obj) throws InvalidInputDataException; 40 | 41 | /** Sends this net id over the specified connection */ 42 | public abstract void send(ActiveConnection connection, T obj); 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetKeyMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.function.Function; 11 | 12 | public abstract class NetKeyMapper { 13 | public static final int LENGTH_DYNAMIC = TreeNetIdBase.DYNAMIC_LENGTH; 14 | 15 | public final Class clazz; 16 | 17 | /** The known length in bytes of this key, or {@link #LENGTH_DYNAMIC} if this isn't a constant. */ 18 | public final int length; 19 | 20 | public NetKeyMapper(Class clazz, int length) { 21 | this.clazz = clazz; 22 | this.length = length; 23 | } 24 | 25 | public NetKeyMapper(Class clazz) { 26 | this(clazz, LENGTH_DYNAMIC); 27 | } 28 | 29 | /** Reads the value for the key from the buffer, optionally using the parent keys. 30 | *

31 | * Note that any of the parent keys might be null if they failed to read! 32 | * 33 | * @return null if the key couldn't be found in whatever context this requires. */ 34 | public abstract T read(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException; 35 | 36 | public abstract void write(NetByteBuf buffer, IMsgWriteCtx ctx, T value); 37 | 38 | public static class ToString extends NetKeyMapper { 39 | 40 | private final Function toStringMapper; 41 | private final Function fromStringMapper; 42 | 43 | public ToString(Class clazz, Function toString, Function fromString) { 44 | super(clazz); 45 | toStringMapper = toString; 46 | fromStringMapper = fromString; 47 | } 48 | 49 | @Override 50 | public T read(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 51 | String s = MsgUtil.readUTF(buffer); 52 | T read = fromStringMapper.apply(s); 53 | if (read == null) { 54 | throw new InvalidInputDataException("Cannot read the string value '" + read + "' as " + clazz); 55 | } 56 | return read; 57 | } 58 | 59 | @Override 60 | public void write(NetByteBuf buffer, IMsgWriteCtx ctx, T value) { 61 | MsgUtil.writeUTF(buffer, toStringMapper.apply(value)); 62 | } 63 | } 64 | 65 | public static class OfEnum> extends NetKeyMapper { 66 | 67 | public OfEnum(Class clazz) { 68 | super(clazz, 4); 69 | } 70 | 71 | @Override 72 | public E read(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 73 | return buffer.readEnumConstant(clazz); 74 | } 75 | 76 | @Override 77 | public void write(NetByteBuf buffer, IMsgWriteCtx ctx, E value) { 78 | buffer.writeEnumConstant(value); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetObjectCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.Map; 11 | import java.util.Objects; 12 | import java.util.function.Function; 13 | 14 | import javax.annotation.Nullable; 15 | 16 | import net.minecraft.util.Identifier; 17 | import alexiil.mc.lib.net.NetObjectCacheBase.Data; 18 | import it.unimi.dsi.fastutil.Hash; 19 | 20 | public final class NetObjectCache extends NetObjectCacheBase { 21 | 22 | static final boolean DEBUG = LibNetworkStack.DEBUG || Boolean.getBoolean("libnetworkstack.cache.debug"); 23 | 24 | public interface IEntrySerialiser { 25 | void write(T obj, ActiveConnection connection, NetByteBuf buffer); 26 | 27 | T read(ActiveConnection connection, NetByteBuf buffer) throws InvalidInputDataException; 28 | } 29 | 30 | private final ParentNetId netIdParent; 31 | private final NetIdData netIdPutCacheEntry; 32 | private final NetIdData netIdRemoveCacheEntry; 33 | 34 | public NetObjectCache(ParentNetId parent, Hash.Strategy equality, IEntrySerialiser serialiser) { 35 | super(equality, serialiser); 36 | this.netIdParent = parent; 37 | this.netIdPutCacheEntry = netIdParent.idData("put").setReceiver(this::receivePutCacheEntry); 38 | this.netIdRemoveCacheEntry = netIdParent.idData("remove").setReceiver(this::receiveRemoveCacheEntry); 39 | } 40 | 41 | /** @see NetIdBase#notBuffered() */ 42 | public void notBuffered() { 43 | netIdPutCacheEntry.notBuffered(); 44 | netIdRemoveCacheEntry.notBuffered(); 45 | } 46 | 47 | public static NetObjectCache createMappedIdentifier( 48 | ParentNetId parent, Function nameGetter, Function objectGetter 49 | ) { 50 | return new NetObjectCache<>(parent, new Hash.Strategy() { 51 | @Override 52 | public int hashCode(T o) { 53 | return o == null ? 0 : nameGetter.apply(o).hashCode(); 54 | } 55 | 56 | @Override 57 | public boolean equals(T a, T b) { 58 | if (a == null || b == null) { 59 | return a == b; 60 | } 61 | return Objects.equals(nameGetter.apply(a), nameGetter.apply(b)); 62 | } 63 | }, new IEntrySerialiser() { 64 | @Override 65 | public void write(T obj, ActiveConnection connection, NetByteBuf buffer) { 66 | buffer.writeIdentifier(nameGetter.apply(obj)); 67 | } 68 | 69 | @Override 70 | public T read(ActiveConnection connection, NetByteBuf buffer) throws InvalidInputDataException { 71 | Identifier id = buffer.readIdentifierSafe(); 72 | if (id == null) { 73 | return null; 74 | } 75 | return objectGetter.apply(id); 76 | } 77 | }); 78 | } 79 | 80 | public static NetObjectCache createMappedIdentifier( 81 | ParentNetId parent, Function nameGetter, Map map 82 | ) { 83 | return createMappedIdentifier(parent, nameGetter, map::get); 84 | } 85 | 86 | private void receivePutCacheEntry(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 87 | int id = buffer.readInt(); 88 | T obj = serialiser.read(ctx.getConnection(), buffer); 89 | if (DEBUG) { 90 | LibNetworkStack.LOGGER.info( 91 | "[cache] " + ctx.getConnection() + " " + netIdParent + " Read new ID " + id + " for object " + obj 92 | ); 93 | } 94 | getData(ctx.getConnection()).idToObj.put(id, obj); 95 | } 96 | 97 | private void receiveRemoveCacheEntry(NetByteBuf buffer, IMsgReadCtx ctx) { 98 | 99 | } 100 | 101 | @Override 102 | NetObjectCacheBase.Data newData(ActiveConnection connection) { 103 | if (DEBUG) { 104 | LibNetworkStack.LOGGER.info("[cache] " + connection + " " + netIdParent + " Created a new cache."); 105 | } 106 | return super.newData(connection); 107 | } 108 | 109 | public int getId(ActiveConnection connection, T obj) { 110 | Data data = getData(connection); 111 | int id = data.objToId.getInt(obj); 112 | if (id < 0) { 113 | id = data.objToId.size(); 114 | data.objToId.put(obj, id); 115 | final int i = id; 116 | if (DEBUG) { 117 | LibNetworkStack.LOGGER 118 | .info("[cache] " + connection + " " + netIdParent + " Sending new ID " + i + " for object " + obj); 119 | } 120 | netIdPutCacheEntry.send(connection, (buffer, ctx) -> { 121 | buffer.writeInt(i); 122 | serialiser.write(obj, connection, buffer); 123 | }); 124 | } 125 | return id; 126 | } 127 | 128 | @Nullable 129 | public T getObj(ActiveConnection connection, int id) { 130 | NetObjectCache.Data data = getData(connection); 131 | if (DEBUG && !data.idToObj.containsKey(id)) { 132 | LibNetworkStack.LOGGER.info("[cache] " + connection + " " + netIdParent + " Unknown ID " + id + "!"); 133 | } 134 | return data.idToObj.get(id); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetObjectCacheBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import alexiil.mc.lib.net.NetObjectCache.IEntrySerialiser; 11 | 12 | import it.unimi.dsi.fastutil.Hash; 13 | import it.unimi.dsi.fastutil.Hash.Strategy; 14 | import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 15 | import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 16 | import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenCustomHashMap; 17 | import it.unimi.dsi.fastutil.objects.Object2IntMap; 18 | 19 | abstract class NetObjectCacheBase { 20 | 21 | class Data { 22 | // TODO: Entry removal! 23 | final Int2ObjectMap idToObj = new Int2ObjectOpenHashMap<>(); 24 | final Object2IntMap objToId = new Object2IntLinkedOpenCustomHashMap<>(equality); 25 | 26 | Data(ActiveConnection connection) { 27 | objToId.defaultReturnValue(-1); 28 | } 29 | } 30 | 31 | final Hash.Strategy equality; 32 | final IEntrySerialiser serialiser; 33 | 34 | public NetObjectCacheBase(Strategy equality, IEntrySerialiser serialiser) { 35 | this.equality = equality; 36 | this.serialiser = serialiser; 37 | } 38 | 39 | Data newData(ActiveConnection connection) { 40 | return new Data(connection); 41 | } 42 | 43 | Data getData(ActiveConnection connection) { 44 | return connection.getCacheData(this); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/NetObjectCacheSimple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.Map; 11 | import java.util.Set; 12 | import java.util.function.Function; 13 | 14 | import net.minecraft.util.Identifier; 15 | import net.minecraft.util.Util; 16 | 17 | import alexiil.mc.lib.net.NetObjectCache.IEntrySerialiser; 18 | 19 | import it.unimi.dsi.fastutil.Hash.Strategy; 20 | 21 | public class NetObjectCacheSimple extends NetObjectCacheBase { 22 | 23 | public NetObjectCacheSimple(Strategy equality, IEntrySerialiser serialiser) { 24 | super(equality, serialiser); 25 | } 26 | 27 | public NetObjectCacheSimple(Map map, Function reverse) { 28 | super(Util.identityHashStrategy(), new IEntrySerialiser() { 29 | @Override 30 | public void write(T obj, ActiveConnection connection, NetByteBuf buffer) { 31 | buffer.writeString(reverse.apply(obj)); 32 | } 33 | 34 | @Override 35 | public T read(ActiveConnection connection, NetByteBuf buffer) throws InvalidInputDataException { 36 | String str = buffer.readString(); 37 | T obj = map.get(str); 38 | if (obj == null) { 39 | throw new InvalidInputDataException("Unknown object '" + str + "'"); 40 | } 41 | return obj; 42 | } 43 | }); 44 | } 45 | 46 | public static NetObjectCacheSimple createMappedIdentifier( 47 | Map map, Function reverse 48 | ) { 49 | return new NetObjectCacheSimple<>(Util.identityHashStrategy(), new IEntrySerialiser() { 50 | @Override 51 | public void write(T obj, ActiveConnection connection, NetByteBuf buffer) { 52 | buffer.writeIdentifier(reverse.apply(obj)); 53 | } 54 | 55 | @Override 56 | public T read(ActiveConnection connection, NetByteBuf buffer) throws InvalidInputDataException { 57 | Identifier id = buffer.readIdentifierSafe(); 58 | T obj = map.get(id); 59 | if (obj == null) { 60 | throw new InvalidInputDataException("Unknown object '" + id + "'"); 61 | } 62 | return obj; 63 | } 64 | }); 65 | } 66 | 67 | public static NetObjectCacheSimple createPartiallyMappedIdentifier( 68 | Map map, Function reverse 69 | ) { 70 | return new NetObjectCacheSimple<>(Util.identityHashStrategy(), new IEntrySerialiser() { 71 | @Override 72 | public void write(T obj, ActiveConnection connection, NetByteBuf buffer) { 73 | buffer.writeIdentifier(reverse.apply(obj)); 74 | } 75 | 76 | @Override 77 | public T read(ActiveConnection connection, NetByteBuf buffer) throws InvalidInputDataException { 78 | Identifier id = buffer.readIdentifierSafe(); 79 | T obj = map.get(id); 80 | if (obj == null) { 81 | throw new InvalidInputDataException("Unknown object '" + id + "'"); 82 | } 83 | return obj; 84 | } 85 | }); 86 | } 87 | 88 | public static NetObjectCacheSimple createIdentifierSet(Set set) { 89 | return new NetObjectCacheSimple<>(Util.identityHashStrategy(), new IEntrySerialiser() { 90 | @Override 91 | public void write(Identifier obj, ActiveConnection connection, NetByteBuf buffer) { 92 | buffer.writeIdentifier(obj); 93 | } 94 | 95 | @Override 96 | public Identifier read(ActiveConnection connection, NetByteBuf buffer) throws InvalidInputDataException { 97 | Identifier id = buffer.readIdentifierSafe(); 98 | if (!set.contains(id)) { 99 | throw new InvalidInputDataException("Unknown object '" + id + "'"); 100 | } 101 | return id; 102 | } 103 | }); 104 | } 105 | 106 | public void write(T obj, NetByteBuf buffer, IMsgWriteCtx ctx) { 107 | NetObjectCacheBase.Data cacheData = ctx.getConnection().getCacheData(this); 108 | int id = cacheData.objToId.getInt(obj); 109 | if (id == -1) { 110 | buffer.writeBoolean(true); 111 | int newId = cacheData.objToId.size(); 112 | cacheData.objToId.put(obj, newId); 113 | buffer.writeVarUnsignedInt(newId); 114 | serialiser.write(obj, ctx.getConnection(), buffer); 115 | } else { 116 | buffer.writeBoolean(false); 117 | buffer.writeVarUnsignedInt(id); 118 | } 119 | } 120 | 121 | public T read(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 122 | NetObjectCacheBase.Data cacheData = ctx.getConnection().getCacheData(this); 123 | boolean isNew = buffer.readBoolean(); 124 | int id = buffer.readVarUnsignedInt(); 125 | if (isNew) { 126 | T obj = serialiser.read(ctx.getConnection(), buffer); 127 | cacheData.idToObj.put(id, obj); 128 | return obj; 129 | } else { 130 | T obj = cacheData.idToObj.get(id); 131 | if (obj == null) { 132 | throw new InvalidInputDataException("Unknown ID " + id); 133 | } 134 | return obj; 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ParentDynamicNetId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.List; 11 | import java.util.function.Function; 12 | 13 | import alexiil.mc.lib.net.DynamicNetLink.IDynamicLinkFactory; 14 | 15 | public final class ParentDynamicNetId extends ParentNetIdDuel { 16 | 17 | public final DynamicNetId childId; 18 | private final Function childGetter; 19 | 20 | public ParentDynamicNetId(ParentNetIdSingle

parent, String name, DynamicNetId childId, 21 | Function childGetter) { 22 | super(parent, name, childId.clazz); 23 | this.childId = childId; 24 | this.childGetter = childGetter; 25 | } 26 | 27 | @Override 28 | TreeNetIdBase getChild(String childName) { 29 | if (childName.equals(childId.name)) { 30 | return childId; 31 | } 32 | return null; 33 | } 34 | 35 | @Override 36 | protected P extractParent(C value) { 37 | throw new IllegalStateException("Dynamic Net ID's must be written with the dynamic variant!"); 38 | } 39 | 40 | @Override 41 | protected void writeContext0(NetByteBuf buffer, IMsgWriteCtx ctx, C value) { 42 | throw new IllegalStateException("Dynamic Net ID's must be written with the dynamic variant!"); 43 | } 44 | 45 | @Override 46 | public void writeDynamicContext(CheckingNetByteBuf buffer, IMsgWriteCtx ctx, C value, List resolvedPath) { 47 | throw new IllegalStateException("This should never be called by DynamicNetId!"); 48 | } 49 | 50 | @Override 51 | protected C readContext(NetByteBuf buffer, IMsgReadCtx ctx, P parentValue) throws InvalidInputDataException { 52 | return childGetter.apply(parentValue); 53 | } 54 | 55 | public DynamicNetLink link(P parent, C child) { 56 | return new DynamicNetLink<>(this, parent, child); 57 | } 58 | 59 | public IDynamicLinkFactory linkFactory(P parent) { 60 | return child -> link(parent, child); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ParentNetId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import javax.annotation.Nullable; 14 | 15 | /** A {@link ParentNetIdBase parent node} that doesn't write any header information for it's children: the static 16 | * context (given by the {@link IMsgReadCtx} or {@link IMsgWriteCtx}) should be enough. */ 17 | public final class ParentNetId extends ParentNetIdBase { 18 | 19 | private final Map leafChildren = new HashMap<>(); 20 | private final Map branchChildren = new HashMap<>(); 21 | 22 | /** @param parent The parent. If this is null then this is a root node, and therefore is managed separately. (In all 23 | * other cases you probably want to call {@link #child(String)} instead of using this). */ 24 | public ParentNetId(@Nullable ParentNetId parent, String name) { 25 | super(parent, name, 0); 26 | if (parent != null) { 27 | parent.addChild(this); 28 | } 29 | } 30 | 31 | void addChild(NetIdSeparate netId) { 32 | if (leafChildren.containsKey(netId.name) || branchChildren.containsKey(netId.name)) { 33 | throw new IllegalStateException("There is already a child with the name " + netId.name + "!"); 34 | } 35 | leafChildren.put(netId.name, netId); 36 | } 37 | 38 | void addChild(ParentNetIdBase netId) { 39 | if (leafChildren.containsKey(netId.name) || branchChildren.containsKey(netId.name)) { 40 | throw new IllegalStateException("There is already a child with the name " + netId.name + "!"); 41 | } 42 | branchChildren.put(netId.name, netId); 43 | } 44 | 45 | @Override 46 | TreeNetIdBase getChild(String childName) { 47 | NetIdSeparate leaf = leafChildren.get(childName); 48 | return leaf != null ? leaf : branchChildren.get(childName); 49 | } 50 | 51 | /** Returns a new {@link NetIdData} (with this as it's parent) that can write an arbitrary length of data. */ 52 | public NetIdData idData(String name) { 53 | return new NetIdData(this, name, TreeNetIdBase.DYNAMIC_LENGTH); 54 | } 55 | 56 | /** Returns a new {@link NetIdData} (with this as it's parent) that must write exactly the given "length" bytes of 57 | * data. 58 | * 59 | * @param dataLength The exact number of bytes that must be written out. (This is so that the exact length isn't 60 | * written out each time as it's known in advance). There's generally no reason to use this unless this 61 | * is for a very small (and frequently sent) packet, as otherwise the space gained will be negligible. */ 62 | public NetIdData idData(String name, int dataLength) { 63 | return new NetIdData(this, name, dataLength); 64 | } 65 | 66 | /** Returns a new {@link NetIdSignal} (with this as it's parent) that won't write any data out: instead the mere 67 | * presence of this packet should convey all of the information required. */ 68 | public NetIdSignal idSignal(String name) { 69 | return new NetIdSignal(this, name); 70 | } 71 | 72 | /** Returns a new {@link ParentNetId} with this as it's child. This is mostly useful for organising different 73 | * packets or mods from the root node. */ 74 | public ParentNetId child(String childName) { 75 | return new ParentNetId(this, childName); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ParentNetIdBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import javax.annotation.Nullable; 11 | 12 | /** The base type for all parent nodes - that is nodes used for organising the data that is sent (or for writing header 13 | * information), but you can't send or receive data through this. */ 14 | public abstract class ParentNetIdBase extends TreeNetIdBase { 15 | 16 | ParentNetIdBase(ParentNetIdBase parent, String name, int thisLength) { 17 | super(parent, name, thisLength); 18 | } 19 | 20 | @Override 21 | final int getFinalFlags() { 22 | return NetIdBase.FLAG_IS_PARENT; 23 | } 24 | 25 | @Nullable 26 | abstract TreeNetIdBase getChild(String childName); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ParentNetIdCast.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | public final class ParentNetIdCast extends ParentNetIdDuel { 11 | 12 | public ParentNetIdCast(ParentNetIdSingle parent, String name, Class clazz) { 13 | super(parent, name, clazz, 0); 14 | } 15 | 16 | @Override 17 | protected Super extractParent(Sub value) { 18 | return value; 19 | } 20 | 21 | @Override 22 | protected void writeContext0(NetByteBuf buffer, IMsgWriteCtx ctx, Sub value) { 23 | // Nothing to write 24 | } 25 | 26 | @Override 27 | protected Sub readContext(NetByteBuf buffer, IMsgReadCtx ctx, Super parentValue) throws InvalidInputDataException { 28 | if (clazz.isInstance(parentValue)) { 29 | return clazz.cast(parentValue); 30 | } else { 31 | return null; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ParentNetIdDuel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.List; 11 | 12 | public abstract class ParentNetIdDuel extends ParentNetIdSingle { 13 | 14 | public final ParentNetIdSingle parent; 15 | 16 | public ParentNetIdDuel(ParentNetIdSingle parent, String name, Class clazz, int length) { 17 | super(parent, clazz, name, length); 18 | this.parent = parent; 19 | if (!(this instanceof ResolvedParentNetId) && !(this instanceof ResolvedDynamicNetId)) { 20 | parent.addChild(this); 21 | } 22 | } 23 | 24 | public ParentNetIdDuel(ParentNetIdSingle parent, String name, Class clazz) { 25 | this(parent, name, clazz, DYNAMIC_LENGTH); 26 | } 27 | 28 | @Override 29 | protected String getPrintableName() { 30 | return getRealClassName() + "<" + parent.clazz.getSimpleName() + ", " + clazz.getSimpleName() + ">"; 31 | } 32 | 33 | @Override 34 | protected final void writeContext(NetByteBuf buffer, IMsgWriteCtx ctx, T value) { 35 | writeContext0(buffer, ctx, value); 36 | } 37 | 38 | @Override 39 | final void writeContextCall(CheckingNetByteBuf buffer, IMsgWriteCtx ctx, T value) { 40 | parent.writeContextCall(buffer, ctx, extractParent(value)); 41 | super.writeContextCall(buffer, ctx, value); 42 | } 43 | 44 | @Override 45 | protected void writeDynamicContext( 46 | CheckingNetByteBuf buffer, IMsgWriteCtx ctx, T value, List resolvedPath 47 | ) { 48 | parent.writeDynamicContext(buffer, ctx, extractParent(value), resolvedPath); 49 | resolvedPath.add(this); 50 | if (buffer.hasTypeData()) { 51 | buffer.writeMarkerId(InternalMsgUtil.getWriteId(ctx.getConnection(), this, new NetIdPath(resolvedPath))); 52 | } 53 | writeContext0(buffer, ctx, value); 54 | } 55 | 56 | protected abstract Parent extractParent(T value); 57 | 58 | protected abstract void writeContext0(NetByteBuf buffer, IMsgWriteCtx ctx, T value); 59 | 60 | @Override 61 | protected final T readContext(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 62 | throw new UnsupportedOperationException("Call readContextCall instead!"); 63 | } 64 | 65 | @Override 66 | final T readContextCall(CheckingNetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 67 | Parent p = parent.readContextCall(buffer, ctx); 68 | if (p == null) { 69 | return null; 70 | } 71 | if (!(this instanceof ParentDynamicNetId) && !(this instanceof ResolvedDynamicNetId)) { 72 | buffer.readMarkerId(ctx, this); 73 | } 74 | return readContext(buffer, ctx, p); 75 | } 76 | 77 | /** @return The read value. 78 | * @throws InvalidInputDataException if the byte buffer contained invalid data. */ 79 | protected abstract T readContext(NetByteBuf buffer, IMsgReadCtx ctx, Parent parentValue) 80 | throws InvalidInputDataException; 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ParentNetIdDuelDirect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | public final class ParentNetIdDuelDirect extends ParentNetIdDuel { 11 | 12 | public ParentNetIdDuelDirect(ParentNetIdSingle parent, String name) { 13 | super(parent, name, parent.clazz, 0); 14 | } 15 | 16 | @Override 17 | protected T extractParent(T value) { 18 | return value; 19 | } 20 | 21 | @Override 22 | protected void writeContext0(NetByteBuf buffer, IMsgWriteCtx ctx, T value) { 23 | // NO-OP 24 | } 25 | 26 | @Override 27 | protected T readContext(NetByteBuf buffer, IMsgReadCtx ctx, T parentValue) throws InvalidInputDataException { 28 | return parentValue; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ParentNetIdExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.function.Function; 11 | 12 | public final class ParentNetIdExtractor extends ParentNetIdDuel { 13 | 14 | private final Function forward; 15 | private final Function backward; 16 | 17 | public ParentNetIdExtractor(ParentNetIdSingle parent, String name, Class clazz, 18 | Function forward, Function backward) { 19 | super(parent, name, clazz, 0); 20 | this.forward = forward; 21 | this.backward = backward; 22 | } 23 | 24 | @Override 25 | protected Parent extractParent(T value) { 26 | return forward.apply(value); 27 | } 28 | 29 | @Override 30 | protected void writeContext0(NetByteBuf buffer, IMsgWriteCtx ctx, T value) { 31 | // NO-OP 32 | } 33 | 34 | @Override 35 | protected T readContext(NetByteBuf buffer, IMsgReadCtx ctx, Parent parentValue) throws InvalidInputDataException { 36 | return backward.apply(parentValue); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ParentNetIdSingle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.function.Function; 16 | 17 | /** A {@link ParentNetIdBase parent node} that writes out the context for the receiver to obtain the same object. 18 | * 19 | * @param The type of object that will be written and read. This must not have any generic type parameters itself: 20 | * as it must be representable with a {@link Class}. */ 21 | public abstract class ParentNetIdSingle extends ParentNetIdBase { 22 | 23 | /** The type of the object to be written and read. */ 24 | public final Class clazz; 25 | 26 | final Map> leafChildren = new HashMap<>(); 27 | final Map> branchChildren = new HashMap<>(); 28 | 29 | ParentNetIdSingle(ParentNetIdBase parent, Class clazz, String name, int thisLength) { 30 | super(parent, name, thisLength); 31 | this.clazz = clazz; 32 | } 33 | 34 | /** @param clazz The type of the object to be written and read. */ 35 | public ParentNetIdSingle(ParentNetId parent, Class clazz, String name, int thisLength) { 36 | super(parent, name, thisLength); 37 | this.clazz = clazz; 38 | if (parent != null) { 39 | parent.addChild(this); 40 | } 41 | } 42 | 43 | void addChild(NetIdTyped netId) { 44 | if (this instanceof ParentDynamicNetId) { 45 | throw new IllegalArgumentException("ParentDynamicNetId can only have 1 child!"); 46 | } 47 | if (leafChildren.containsKey(netId.name) || branchChildren.containsKey(netId.name)) { 48 | throw new IllegalStateException("There is already a child with the name " + netId.name + "!"); 49 | } 50 | leafChildren.put(netId.name, netId); 51 | } 52 | 53 | void addChild(ParentNetIdDuel netId) { 54 | if (this instanceof ParentDynamicNetId) { 55 | throw new IllegalArgumentException("ParentDynamicNetId can only have 1 child!"); 56 | } 57 | if (leafChildren.containsKey(netId.name) || branchChildren.containsKey(netId.name)) { 58 | throw new IllegalStateException("There is already a child with the name " + netId.name + "!"); 59 | } 60 | branchChildren.put(netId.name, netId); 61 | } 62 | 63 | @Override 64 | TreeNetIdBase getChild(String childName) { 65 | NetIdTyped leaf = leafChildren.get(childName); 66 | return leaf != null ? leaf : branchChildren.get(childName); 67 | } 68 | 69 | @Override 70 | protected String getPrintableName() { 71 | return getRealClassName() + "<" + clazz.getSimpleName() + ">"; 72 | } 73 | 74 | public NetIdDataK idData(String name) { 75 | return new NetIdDataK<>(this, name, TreeNetIdBase.DYNAMIC_LENGTH); 76 | } 77 | 78 | public NetIdDataK idData(String name, int dataLength) { 79 | return new NetIdDataK<>(this, name, dataLength); 80 | } 81 | 82 | public NetIdSignalK idSignal(String name) { 83 | return new NetIdSignalK<>(this, name); 84 | } 85 | 86 | public ParentNetIdCast subType(Class subClass, String subName) { 87 | return new ParentNetIdCast<>(this, subName, subClass); 88 | } 89 | 90 | public ParentNetIdExtractor extractor( 91 | Class targetClass, String subName, Function forward, Function backward 92 | ) { 93 | return new ParentNetIdExtractor<>(this, subName, targetClass, forward, backward); 94 | } 95 | 96 | public ParentNetIdSingle child(String name) { 97 | return new ParentNetIdDuelDirect<>(this, name); 98 | } 99 | 100 | /** @return The read value, or null if the parent couldn't be read. 101 | * @throws InvalidInputDataException if the byte buffer contained invalid data. */ 102 | protected abstract T readContext(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException; 103 | 104 | protected abstract void writeContext(NetByteBuf buffer, IMsgWriteCtx ctx, T value); 105 | 106 | T readContextCall(CheckingNetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 107 | buffer.readMarkerId(ctx, this); 108 | return readContext(buffer, ctx); 109 | } 110 | 111 | void writeContextCall(CheckingNetByteBuf buffer, IMsgWriteCtx ctx, T value) { 112 | if (buffer.hasTypeData()) { 113 | buffer.writeMarkerId(InternalMsgUtil.getWriteId(ctx.getConnection(), this, path)); 114 | } 115 | writeContext(buffer, ctx, value); 116 | } 117 | 118 | protected void writeDynamicContext( 119 | CheckingNetByteBuf buffer, IMsgWriteCtx ctx, T value, List resolvedPath 120 | ) { 121 | writeContextCall(buffer, ctx, value); 122 | Collections.addAll(resolvedPath, this.path.array); 123 | } 124 | 125 | public final void writeKey(CheckingNetByteBuf buffer, IMsgWriteCtx ctx, T value) { 126 | if (pathContainsDynamicParent) { 127 | int wIndex = buffer.writerIndex(); 128 | buffer.writeInt(0); 129 | List nPath = new ArrayList<>(); 130 | writeDynamicContext(buffer, ctx, value, nPath); 131 | nPath.add(this); 132 | TreeNetIdBase[] array = nPath.toArray(new TreeNetIdBase[0]); 133 | NetIdPath resolvedPath = new NetIdPath(array); 134 | buffer.setInt(wIndex, InternalMsgUtil.getWriteId(ctx.getConnection(), this, resolvedPath)); 135 | } else { 136 | writeContext(buffer, ctx, value); 137 | } 138 | } 139 | 140 | public final T readKey(CheckingNetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 141 | if (!pathContainsDynamicParent) { 142 | // Static path 143 | return readContext(buffer, ctx); 144 | } 145 | ActiveConnection connection = ctx.getConnection(); 146 | int pathId = buffer.readInt(); 147 | if (pathId < 0 || pathId >= connection.readMapIds.size()) { 148 | throw new InvalidInputDataException("Unknown/invalid ID " + pathId); 149 | } 150 | TreeNetIdBase readId = connection.readMapIds.get(pathId); 151 | if (!(readId instanceof ParentNetIdSingle)) { 152 | throw new InvalidInputDataException("Not a receiving node: " + readId + " for id " + pathId); 153 | } 154 | ParentNetIdSingle op = (ParentNetIdSingle) readId; 155 | Object read = op.readContext(buffer, ctx); 156 | if (!clazz.isInstance(read)) { 157 | throw new InvalidInputDataException( 158 | "The id sent to us (" + pathId + ") was for a node of a different class! (" + op + ")" 159 | ); 160 | } 161 | return clazz.cast(read); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ResolvedDynamicNetId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | final class ResolvedDynamicNetId extends ParentNetIdDuel { 11 | 12 | final DynamicNetId wrapped; 13 | 14 | public ResolvedDynamicNetId(ParentNetIdSingle parent, DynamicNetId wrapped) { 15 | super(parent, "dynamic", wrapped.clazz, 0); 16 | this.wrapped = wrapped; 17 | } 18 | 19 | @Override 20 | TreeNetIdBase getChild(String childName) { 21 | return wrapped.getChild(childName); 22 | } 23 | 24 | @Override 25 | protected T extractParent(T value) { 26 | return value; 27 | } 28 | 29 | @Override 30 | protected void writeContext0(NetByteBuf buffer, IMsgWriteCtx ctx, T value) { 31 | throw new IllegalStateException("Resolved Net ID's must never be written as they are only for reading!"); 32 | } 33 | 34 | @Override 35 | protected T readContext(NetByteBuf buffer, IMsgReadCtx ctx, T parentValue) throws InvalidInputDataException { 36 | return parentValue; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ResolvedNetId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | final class ResolvedNetId extends NetIdTyped { 11 | 12 | final NetIdTyped wrapped; 13 | 14 | public ResolvedNetId(ParentNetIdSingle parent, NetIdTyped wrapped) { 15 | super(parent, wrapped.name, wrapped.length); 16 | this.wrapped = wrapped; 17 | changeFlag(wrapped.getFinalFlags()); 18 | } 19 | 20 | @Override 21 | protected void receive(NetByteBuf buffer, IMsgReadCtx ctx, T obj) throws InvalidInputDataException { 22 | receive0(buffer, ctx, obj, wrapped); 23 | } 24 | 25 | @Override 26 | public void send(ActiveConnection connection, T obj) { 27 | throw new IllegalStateException( 28 | "You can never write out a Resolved Net ID!" 29 | + "\n(How did you even call this anyway? This is meant to be an internal class!)" 30 | ); 31 | } 32 | 33 | private static void receive0(NetByteBuf buffer, IMsgReadCtx ctx, Object obj, NetIdTyped wrapped) 34 | throws InvalidInputDataException { 35 | wrapped.receive(buffer, ctx, wrapped.parent.clazz.cast(obj)); 36 | } 37 | 38 | @Override 39 | public ResolvedNetId withoutBuffering() { 40 | notBuffered(); 41 | return this; 42 | } 43 | 44 | @Override 45 | public ResolvedNetId withTinySize() { 46 | setTinySize(); 47 | return this; 48 | } 49 | 50 | @Override 51 | public ResolvedNetId withNormalSize() { 52 | setNormalSize(); 53 | return this; 54 | } 55 | 56 | @Override 57 | public ResolvedNetId withLargeSize() { 58 | setLargeSize(); 59 | return this; 60 | } 61 | 62 | @Override 63 | public ResolvedNetId toClientOnly() { 64 | _toClientOnly(); 65 | return this; 66 | } 67 | 68 | @Override 69 | public ResolvedNetId toServerOnly() { 70 | _toServerOnly(); 71 | return this; 72 | } 73 | 74 | @Override 75 | public ResolvedNetId toEitherSide() { 76 | _toEitherSide(); 77 | return this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/ResolvedParentNetId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | final class ResolvedParentNetId extends ParentNetIdDuel { 11 | 12 | public final ParentNetIdDuel reader; 13 | 14 | ResolvedParentNetId(ParentNetIdSingle parent, ParentNetIdDuel reader) { 15 | super(parent, reader.name, reader.clazz, reader.length); 16 | this.reader = reader; 17 | } 18 | 19 | @Override 20 | TreeNetIdBase getChild(String childName) { 21 | return reader.getChild(childName); 22 | } 23 | 24 | @Override 25 | protected Parent extractParent(T value) { 26 | throw new IllegalStateException("ResolvedParentNetId must only be used for reading!"); 27 | } 28 | 29 | @Override 30 | protected void writeContext0(NetByteBuf buffer, IMsgWriteCtx ctx, T value) { 31 | throw new IllegalStateException("ResolvedParentNetId must only be used for reading!"); 32 | } 33 | 34 | @Override 35 | protected T readContext(NetByteBuf buffer, IMsgReadCtx ctx, Parent parentValue) throws InvalidInputDataException { 36 | return reader.readContext(buffer, ctx, parentValue); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/SingleConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | import it.unimi.dsi.fastutil.ints.IntOpenHashSet; 11 | import it.unimi.dsi.fastutil.ints.IntSet; 12 | 13 | public abstract class SingleConnection { 14 | 15 | final IntSet clientKnownIds = new IntOpenHashSet(); 16 | 17 | public abstract void send(NetByteBuf packet); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/TreeNetIdBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net; 9 | 10 | /** The base class for all networking ID's. Most of the time you should use one of the subclasses: either the parent 11 | * node {@link ParentNetIdBase}, or the leaf node {@link NetIdBase}. */ 12 | public abstract class TreeNetIdBase { 13 | 14 | public static final int DYNAMIC_LENGTH = -1; 15 | 16 | /** Used by {@link InternalMsgUtil} to assert that this is a parent. */ 17 | static final int PARENT_LENGTH = -2; 18 | 19 | static final int MAXIMUM_PACKET_LENGTH = 0x1_00_00_00; 20 | 21 | /** The name - must be unique to the parent that uses it, so for top level mod packets this MUST include the 22 | * modid. */ 23 | public final String name; 24 | 25 | /** Used for logging and display purposes only. */ 26 | public final String fullName; 27 | 28 | /** The length of this ID alone. Might not be a sensible number even if {@link #totalLength} is. */ 29 | public final int length; 30 | 31 | /** Full length including all parents. */ 32 | public final int totalLength; 33 | 34 | public final ParentNetIdBase parent; 35 | 36 | final boolean pathContainsDynamicParent; 37 | final NetIdPath path; 38 | 39 | public TreeNetIdBase(ParentNetIdBase parent, String name, int thisLength) { 40 | this.parent = parent; 41 | this.name = name; 42 | this.fullName = parent == null ? name : parent.fullName + "." + name; 43 | this.length = thisLength; 44 | if (thisLength == DYNAMIC_LENGTH) { 45 | this.totalLength = DYNAMIC_LENGTH; 46 | } else if (parent == null) { 47 | this.totalLength = thisLength; 48 | } else if (parent.totalLength == DYNAMIC_LENGTH) { 49 | this.totalLength = DYNAMIC_LENGTH; 50 | } else { 51 | this.totalLength = thisLength + parent.totalLength; 52 | } 53 | if (totalLength != DYNAMIC_LENGTH) { 54 | if (totalLength > MAXIMUM_PACKET_LENGTH) { 55 | throw new IllegalArgumentException( 56 | "The length (" + totalLength + ") exceeded the maximum packet length (" + MAXIMUM_PACKET_LENGTH 57 | + ")" 58 | ); 59 | } else if (totalLength < 0) { 60 | throw new IllegalArgumentException( 61 | "The length (" + totalLength + ") has likely overflowed, exceeded the maximum packet length (" 62 | + MAXIMUM_PACKET_LENGTH + ")" 63 | ); 64 | } 65 | } 66 | 67 | if (parent == null) { 68 | path = new NetIdPath(this); 69 | pathContainsDynamicParent = this instanceof DynamicNetId; 70 | } else { 71 | path = parent.path.withChild(this); 72 | pathContainsDynamicParent = parent.pathContainsDynamicParent || this instanceof DynamicNetId; 73 | } 74 | } 75 | 76 | @Override 77 | public final String toString() { 78 | return getPrintableName() + " '" + fullName + "'"; 79 | } 80 | 81 | protected String getPrintableName() { 82 | return getRealClassName(); 83 | } 84 | 85 | protected final String getRealClassName() { 86 | Class cls = getClass(); 87 | while (cls.isAnonymousClass()) { 88 | cls = cls.getSuperclass(); 89 | } 90 | return cls.getSimpleName(); 91 | } 92 | 93 | @Override 94 | public final boolean equals(Object obj) { 95 | return this == obj; 96 | } 97 | 98 | @Override 99 | public final int hashCode() { 100 | return System.identityHashCode(this); 101 | } 102 | 103 | /** @return True if the total length of this NetId (the entire path, including it's header) is a fixed length (and 104 | * so the length doesn't need to be written out each time it is sent), or false otherwise. */ 105 | public final boolean hasFixedLength() { 106 | return totalLength != DYNAMIC_LENGTH; 107 | } 108 | 109 | final int getLengthForPacketAlloc() { 110 | return this instanceof NetIdBase ? totalLength : PARENT_LENGTH; 111 | } 112 | 113 | /** @return The set of flags for this net id. Once this method is called it must always return the same value every 114 | * time afterwards! */ 115 | abstract int getFinalFlags(); 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/impl/ActiveClientConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.impl; 9 | 10 | import net.minecraft.client.MinecraftClient; 11 | import net.minecraft.client.network.ClientPlayNetworkHandler; 12 | import net.minecraft.client.render.RenderTickCounter; 13 | import net.minecraft.entity.player.PlayerEntity; 14 | import net.minecraft.network.packet.Packet; 15 | import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; 16 | import net.minecraft.util.Util; 17 | 18 | import alexiil.mc.lib.net.EnumNetSide; 19 | import alexiil.mc.lib.net.NetByteBuf; 20 | import alexiil.mc.lib.net.mixin.impl.MinecraftClientMixin; 21 | 22 | public class ActiveClientConnection extends ActiveMinecraftConnection { 23 | 24 | private static final int DATAPOINT_SATURATION_COUNT = 30; 25 | 26 | public final ClientPlayNetworkHandler netHandler; 27 | 28 | private long currentServerTick = Long.MIN_VALUE, prevServerTick; 29 | private long currentServerSendTime, prevServerSendTime; 30 | private long currentServerReceiveTime, prevServerReceiveTime; 31 | 32 | private double averageSendTimeDelta; 33 | private double averageReceiveTimeDelta; 34 | 35 | private int datapointCount; 36 | 37 | private long lastClientMs = Long.MIN_VALUE; 38 | 39 | private long smoothedServerTickValue = Long.MIN_VALUE; 40 | private double smoothedServerTickDelta = 0; 41 | 42 | public ActiveClientConnection(ClientPlayNetworkHandler netHandler) { 43 | this.netHandler = netHandler; 44 | } 45 | 46 | @Override 47 | protected Packet toNormalPacket(NetByteBuf data) { 48 | return new CustomPayloadC2SPacket(PACKET_ID, data); 49 | } 50 | 51 | @Override 52 | protected Packet toCompactPacket(int receiverId, NetByteBuf data) { 53 | byte[] bytes = new byte[data.readableBytes()]; 54 | data.readBytes(bytes); 55 | return new CompactDataPacketToServer(receiverId, bytes); 56 | } 57 | 58 | @Override 59 | protected void sendPacket(Packet packet) { 60 | netHandler.sendPacket(packet); 61 | } 62 | 63 | @Override 64 | public EnumNetSide getNetSide() { 65 | return EnumNetSide.CLIENT; 66 | } 67 | 68 | @Override 69 | public PlayerEntity getPlayer() { 70 | return MinecraftClient.getInstance().player; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | // There's nothing really all that useful to show here - there will normally be only one. 76 | String hex = Integer.toHexString(System.identityHashCode(this)); 77 | String prefix = (MinecraftClient.getInstance().getNetworkHandler() == netHandler) ? "The" : "Other"; 78 | return "{" + prefix + " ActiveClientConnection@" + hex + "}"; 79 | } 80 | 81 | void receiveServerTick(long tick, long sendTime) { 82 | if (lastClientMs == Long.MIN_VALUE) { 83 | lastClientMs = Util.getMeasuringTimeMs(); 84 | } 85 | 86 | prevServerTick = currentServerTick; 87 | prevServerSendTime = currentServerSendTime; 88 | prevServerReceiveTime = currentServerReceiveTime; 89 | 90 | currentServerTick = tick; 91 | currentServerSendTime = sendTime; 92 | currentServerReceiveTime = lastClientMs; 93 | 94 | if (datapointCount < 1) { 95 | datapointCount = 1; 96 | 97 | smoothedServerTickValue = tick; 98 | smoothedServerTickDelta = 0; 99 | 100 | return; 101 | } else if (datapointCount == 1) { 102 | datapointCount++; 103 | 104 | averageSendTimeDelta = currentServerSendTime - prevServerSendTime; 105 | averageReceiveTimeDelta = currentServerReceiveTime - prevServerReceiveTime; 106 | 107 | return; 108 | } else if (datapointCount < DATAPOINT_SATURATION_COUNT) { 109 | datapointCount++; 110 | } 111 | 112 | double divisor = DATAPOINT_SATURATION_COUNT; 113 | 114 | averageSendTimeDelta 115 | = (averageSendTimeDelta * datapointCount + currentServerSendTime - prevServerSendTime) / divisor; 116 | 117 | averageReceiveTimeDelta 118 | = (averageReceiveTimeDelta * datapointCount + currentServerReceiveTime - prevServerReceiveTime) / divisor; 119 | } 120 | 121 | /** Called by {@link MinecraftClientMixin} (ONLY) when minecraft increases it's 122 | * {@link MinecraftClient#getTickDelta()} value. Used internally to update the values returned by 123 | * {@link #getSmoothedServerTickValue()} and {@link #getSmoothedServerTickDelta()}. 124 | * 125 | * @param milliseconds The value of {@link Util#getMeasuringTimeMs()} that was passed into 126 | * {@link RenderTickCounter#beginRenderTick(long)}. */ 127 | public void onIncrementMinecraftTickCounter(long milliseconds) { 128 | if (datapointCount <= 0) { 129 | lastClientMs = milliseconds; 130 | return; 131 | } 132 | 133 | if (datapointCount == 1) { 134 | // The average's won't have populated so there's not much we can do 135 | lastClientMs = milliseconds; 136 | return; 137 | } 138 | 139 | // Now we have an average of the tick gap and network gap 140 | // For now just ignore the receive time deltas 141 | 142 | // First try: Very basic, used to test everything 143 | 144 | smoothedServerTickValue = currentServerTick; 145 | smoothedServerTickDelta = 0; 146 | return; 147 | } 148 | 149 | /** Gets the last received tick that the server has sent. It is never normally a good idea to use this method 150 | * directly because this will not return useful values if the network connection isn't perfect. Instead use 151 | * {@link #getSmoothedServerTickValue()} and {@link #getSmoothedServerTickDelta()}. 152 | * 153 | * @return The last received tick from the server, or {@link Long#MIN_VALUE} if the server hasn't sent tick data 154 | * yet. */ 155 | public long getAbsoluteServerTick() { 156 | return currentServerTick; 157 | } 158 | 159 | /** @return The smoothed tick value for the server. This will always lag behind {@link #getAbsoluteServerTick()}, 160 | * but a best-effort is made to try and keep the rate that this changes roughly equal to the server tick 161 | * speed, rather than being solely based on the network connection. */ 162 | public long getSmoothedServerTickValue() { 163 | return smoothedServerTickValue; 164 | } 165 | 166 | /** @return A value between 0 and 1 (to be used along side {@link #getSmoothedServerTickValue()}) which is analogous 167 | * to {@link MinecraftClient#getTickDelta()}. */ 168 | public double getSmoothedServerTickDelta() { 169 | return smoothedServerTickDelta; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/impl/ActiveMinecraftConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.impl; 9 | 10 | import net.minecraft.entity.player.PlayerEntity; 11 | import net.minecraft.network.packet.Packet; 12 | import net.minecraft.util.Identifier; 13 | 14 | import alexiil.mc.lib.net.BufferedConnection; 15 | import alexiil.mc.lib.net.EnumNetSide; 16 | import alexiil.mc.lib.net.LibNetworkStack; 17 | import alexiil.mc.lib.net.NetByteBuf; 18 | import alexiil.mc.lib.net.NetIdData; 19 | 20 | /** A connection to the other side - this is either an {@link ActiveClientConnection} or an 21 | * {@link ActiveServerConnection}. */ 22 | public abstract class ActiveMinecraftConnection extends BufferedConnection { 23 | 24 | // As this is networking we need to ensure that we send as little data as possible 25 | // Which is why we use the full domain, and a fully descriptive path for it 26 | // (What?) 27 | public static final Identifier PACKET_ID = new Identifier("libnetworkstack", "data"); 28 | 29 | private static final NetIdData NET_ID_COMPACT_PACKET = // 30 | McNetworkStack.ROOT.idData("libnetworkstack:compact_id", 4)// 31 | .setReceiver((buffer, ctx) -> { 32 | int theirCustomId = buffer.readInt(); 33 | ActiveMinecraftConnection connection = (ActiveMinecraftConnection) ctx.getConnection(); 34 | connection.theirCustomId = theirCustomId; 35 | 36 | if (LibNetworkStack.DEBUG) { 37 | LibNetworkStack.LOGGER.info(connection + " Received their custom id " + theirCustomId); 38 | } 39 | }); 40 | 41 | static final NetIdData NET_ID_SERVER_TICK = // 42 | McNetworkStack.ROOT.idData("libnetworkstack:server_tick", 16)// 43 | .setReceiver((buffer, ctx) -> { 44 | ctx.assertClientSide(); 45 | long tick = buffer.readLong(); 46 | long sendTime = buffer.readLong(); 47 | ((ActiveClientConnection) ctx.getConnection()).receiveServerTick(tick, sendTime); 48 | }); 49 | 50 | private static final int NET_ID_NOT_OPTIMISED = 0; 51 | 52 | private static final boolean COMPACT_PACKETS = true; 53 | 54 | /** The raw minecraft ID to use if the other side has told us which ID the custom packet is mapped to. */ 55 | private int theirCustomId = NET_ID_NOT_OPTIMISED; 56 | private boolean hasSentCustomId; 57 | 58 | public ActiveMinecraftConnection() { 59 | super(McNetworkStack.ROOT, /* Drop after 3 seconds by default */ 3 * 20); 60 | } 61 | 62 | /** @return The "side" of this connection. This will be {@link EnumNetSide#CLIENT} both when writing client to 63 | * server packets, and when reading packets sent from the server. (And {@link EnumNetSide#SERVER} both when 64 | * writing server to client packets, and when reading client to server packets). */ 65 | @Override 66 | public abstract EnumNetSide getNetSide(); 67 | 68 | @Override 69 | public abstract PlayerEntity getPlayer(); 70 | 71 | @Override 72 | public final void sendRawData0(NetByteBuf data) { 73 | final Packet packet; 74 | if (COMPACT_PACKETS && theirCustomId != NET_ID_NOT_OPTIMISED) { 75 | packet = toCompactPacket(theirCustomId, data); 76 | } else { 77 | data.retain(); 78 | packet = toNormalPacket(data); 79 | } 80 | sendPacket(packet); 81 | } 82 | 83 | @Override 84 | public void tick() { 85 | if (COMPACT_PACKETS && !hasSentCustomId && hasPackets()) { 86 | hasSentCustomId = true; 87 | sendCustomId(); 88 | } 89 | super.tick(); 90 | } 91 | 92 | private void sendCustomId() { 93 | int ourCustomId = this instanceof ActiveServerConnection ? CoreMinecraftNetUtil.serverExpectedId 94 | : CoreMinecraftNetUtil.clientExpectedId; 95 | NET_ID_COMPACT_PACKET.send(this, (buffer, c) -> { 96 | buffer.writeInt(ourCustomId); 97 | }); 98 | if (LibNetworkStack.DEBUG) { 99 | LibNetworkStack.LOGGER.info(this + " Sent our custom id " + ourCustomId); 100 | } 101 | } 102 | 103 | protected abstract Packet toNormalPacket(NetByteBuf data); 104 | 105 | protected abstract Packet toCompactPacket(int receiverId, NetByteBuf data); 106 | 107 | protected abstract void sendPacket(Packet packet); 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/impl/ActiveServerConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.impl; 9 | 10 | import net.minecraft.entity.player.PlayerEntity; 11 | import net.minecraft.network.packet.Packet; 12 | import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; 13 | import net.minecraft.server.network.ServerPlayNetworkHandler; 14 | import net.minecraft.server.network.ServerPlayerEntity; 15 | import net.minecraft.util.Util; 16 | 17 | import alexiil.mc.lib.net.EnumNetSide; 18 | import alexiil.mc.lib.net.NetByteBuf; 19 | 20 | /** A connection on the server side to a specific {@link ServerPlayerEntity}. */ 21 | public class ActiveServerConnection extends ActiveMinecraftConnection { 22 | public final ServerPlayNetworkHandler netHandler; 23 | 24 | private long serverTick = Long.MIN_VALUE; 25 | 26 | public ActiveServerConnection(ServerPlayNetworkHandler netHandler) { 27 | this.netHandler = netHandler; 28 | } 29 | 30 | @Override 31 | protected Packet toNormalPacket(NetByteBuf data) { 32 | return new CustomPayloadS2CPacket(PACKET_ID, data); 33 | } 34 | 35 | @Override 36 | protected Packet toCompactPacket(int receiverId, NetByteBuf data) { 37 | byte[] bytes = new byte[data.readableBytes()]; 38 | data.readBytes(bytes); 39 | return new CompactDataPacketToClient(receiverId, bytes); 40 | } 41 | 42 | @Override 43 | protected void sendPacket(Packet packet) { 44 | netHandler.sendPacket(packet); 45 | } 46 | 47 | @Override 48 | public PlayerEntity getPlayer() { 49 | return netHandler.player; 50 | } 51 | 52 | @Override 53 | public EnumNetSide getNetSide() { 54 | return EnumNetSide.SERVER; 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return "{ActiveServerConnection for " + netHandler.player + "}"; 60 | } 61 | 62 | /** @return The value that the client will use to identify the current server tick. */ 63 | public long getServerTick() { 64 | if (serverTick == Long.MIN_VALUE) { 65 | serverTick = 0; 66 | } 67 | return serverTick; 68 | } 69 | 70 | @Override 71 | protected void sendTickPacket() { 72 | super.sendTickPacket(); 73 | getServerTick(); 74 | if (serverTick != Long.MIN_VALUE) { 75 | final long sv = serverTick; 76 | final long now = Util.getMeasuringTimeMs(); 77 | NET_ID_SERVER_TICK.send(this, (buffer, ctx) -> { 78 | buffer.writeLong(sv); 79 | buffer.writeLong(now); 80 | }); 81 | serverTick++; 82 | if (serverTick < 0) { 83 | // Overflow, this probably won't end well as we use min_value as the sentinal value. 84 | serverTick++; 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/impl/BlockEntityInitialData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.impl; 9 | 10 | import java.util.Collection; 11 | import java.util.Collections; 12 | 13 | import net.fabricmc.fabric.api.networking.v1.PlayerLookup; 14 | 15 | import net.minecraft.block.entity.BlockEntity; 16 | import net.minecraft.server.network.ServerPlayerEntity; 17 | import net.minecraft.server.world.ChunkHolder; 18 | import net.minecraft.server.world.ServerWorld; 19 | import net.minecraft.server.world.ThreadedAnvilChunkStorage; 20 | import net.minecraft.util.math.BlockPos; 21 | import net.minecraft.util.math.ChunkPos; 22 | 23 | import alexiil.mc.lib.net.mixin.api.IThreadedAnvilChunkStorageMixin; 24 | 25 | /** This should be implemented by {@link BlockEntity}s that wish to send their own initial data packet to players 26 | * individually, rather than using {@link BlockEntity#toInitialChunkDataTag()} or 27 | * {@link BlockEntity#toUpdatePacket()}. */ 28 | public interface BlockEntityInitialData { 29 | void sendInitialData(ServerPlayerEntity to); 30 | 31 | /** Like {@link #getPlayersWatching(ServerWorld, BlockPos)}, this only returns any players if the chunk containing 32 | * the block entity has already had the initial data sent from the server. */ 33 | public static Collection getPlayersWatching(BlockEntity be) { 34 | if (!(be.getWorld() instanceof ServerWorld svWorld)) { 35 | throw new IllegalArgumentException("Players can only watch server-sided block entities!"); 36 | } 37 | return getPlayersWatching(svWorld, be.getPos()); 38 | } 39 | 40 | /** This only returns any players if the chunk containing the block entity has already had the initial data sent 41 | * from the server. */ 42 | public static Collection getPlayersWatching(ServerWorld svWorld, BlockPos pos) { 43 | // BUGFIX: for some reason minecraft occasionally ticks block entities with players in range 44 | // But *hasn't* sent the initial chunk data yet, since it hasn't technically finished loading? 45 | ThreadedAnvilChunkStorage tacs = svWorld.getChunkManager().threadedAnvilChunkStorage; 46 | IThreadedAnvilChunkStorageMixin mixinTacs = (IThreadedAnvilChunkStorageMixin) tacs; 47 | ChunkHolder chunkHolder = mixinTacs.libnetworkstack_getChunkHolder(new ChunkPos(pos)); 48 | if (chunkHolder.getWorldChunk() == null) { 49 | return Collections.emptyList(); 50 | } 51 | // END BUGFIX 52 | 53 | return PlayerLookup.tracking(svWorld, pos); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/impl/CompactDataPacketToClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.impl; 9 | 10 | import java.io.IOException; 11 | 12 | import io.netty.buffer.Unpooled; 13 | 14 | import net.minecraft.client.network.ClientPlayNetworkHandler; 15 | import net.minecraft.network.PacketByteBuf; 16 | 17 | import alexiil.mc.lib.net.NetByteBuf; 18 | 19 | public class CompactDataPacketToClient implements IPacketCustomId { 20 | 21 | private final int serverExpectedId; 22 | private byte[] payload; 23 | 24 | public CompactDataPacketToClient(int serverExpectedId, byte[] payload) { 25 | this.serverExpectedId = serverExpectedId; 26 | this.payload = payload; 27 | } 28 | 29 | /** Used for reading */ 30 | public CompactDataPacketToClient(PacketByteBuf buf) { 31 | serverExpectedId = 0; 32 | payload = new byte[buf.readableBytes()]; 33 | buf.readBytes(payload); 34 | } 35 | 36 | @Override 37 | public int getReadId() { 38 | return serverExpectedId; 39 | } 40 | 41 | @Override 42 | public void write(PacketByteBuf buf) { 43 | buf.writeBytes(payload); 44 | } 45 | 46 | @Override 47 | public void apply(ClientPlayNetworkHandler netHandler) { 48 | NetByteBuf buffer = NetByteBuf.asNetByteBuf(Unpooled.wrappedBuffer(payload)); 49 | CoreMinecraftNetUtil.onClientReceivePacket(netHandler, buffer); 50 | buffer.release(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/impl/CompactDataPacketToServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.impl; 9 | 10 | import io.netty.buffer.Unpooled; 11 | 12 | import net.minecraft.network.PacketByteBuf; 13 | import net.minecraft.server.network.ServerPlayNetworkHandler; 14 | 15 | import alexiil.mc.lib.net.NetByteBuf; 16 | 17 | public class CompactDataPacketToServer implements IPacketCustomId { 18 | 19 | private final int clientExpectedId; 20 | private byte[] payload; 21 | 22 | public CompactDataPacketToServer(int clientExpectedId, byte[] payload) { 23 | this.clientExpectedId = clientExpectedId; 24 | this.payload = payload; 25 | } 26 | 27 | /** Used for reading */ 28 | public CompactDataPacketToServer(PacketByteBuf buf) { 29 | clientExpectedId = 0; 30 | payload = new byte[buf.readableBytes()]; 31 | buf.readBytes(payload); 32 | } 33 | 34 | @Override 35 | public int getReadId() { 36 | return clientExpectedId; 37 | } 38 | 39 | @Override 40 | public void write(PacketByteBuf buf) { 41 | buf.writeBytes(payload); 42 | } 43 | 44 | @Override 45 | public void apply(ServerPlayNetworkHandler netHandler) { 46 | NetByteBuf buffer = NetByteBuf.asNetByteBuf(Unpooled.wrappedBuffer(payload)); 47 | CoreMinecraftNetUtil.onServerReceivePacket(netHandler, buffer); 48 | buffer.release(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/impl/IPacketCustomId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.impl; 9 | 10 | import net.minecraft.network.listener.PacketListener; 11 | import net.minecraft.network.packet.Packet; 12 | 13 | public interface IPacketCustomId extends Packet { 14 | /** @return The integer ID that the other side is expecting. */ 15 | int getReadId(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/impl/McNetworkStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.impl; 9 | 10 | import java.util.Objects; 11 | 12 | import net.minecraft.block.entity.BlockEntity; 13 | import net.minecraft.entity.Entity; 14 | import net.minecraft.entity.player.PlayerEntity; 15 | import net.minecraft.item.Item; 16 | import net.minecraft.item.ItemStack; 17 | import net.minecraft.nbt.NbtCompound; 18 | import net.minecraft.screen.ScreenHandler; 19 | 20 | import alexiil.mc.lib.net.ActiveConnection; 21 | import alexiil.mc.lib.net.IMsgReadCtx; 22 | import alexiil.mc.lib.net.IMsgWriteCtx; 23 | import alexiil.mc.lib.net.InvalidInputDataException; 24 | import alexiil.mc.lib.net.MessageContext; 25 | import alexiil.mc.lib.net.NetByteBuf; 26 | import alexiil.mc.lib.net.NetObjectCache; 27 | import alexiil.mc.lib.net.NetObjectCache.IEntrySerialiser; 28 | import alexiil.mc.lib.net.ParentNetId; 29 | import alexiil.mc.lib.net.ParentNetIdSingle; 30 | 31 | import it.unimi.dsi.fastutil.Hash; 32 | 33 | /** Holder for everything related to a normal minecraft connection. */ 34 | public final class McNetworkStack { 35 | private McNetworkStack() {} 36 | 37 | /** The root parent - everything must use as it's parent (somewhere up the chain) or it can never be written out to 38 | * a minecraft connection. */ 39 | public static final ParentNetId ROOT = new ParentNetId(null, ""); 40 | 41 | public static final ParentNetIdSingle BLOCK_ENTITY; 42 | public static final ParentNetIdSingle ENTITY; 43 | 44 | public static final ParentNetIdSingle SCREEN_HANDLER; 45 | 46 | /** @deprecated Use {@link #SCREEN_HANDLER} instead, due to the yarn name change. */ 47 | @Deprecated 48 | public static final ParentNetIdSingle CONTAINER; 49 | 50 | public static final NetObjectCache CACHE_ITEMS_WITHOUT_AMOUNT; 51 | 52 | static { 53 | BLOCK_ENTITY = new ParentNetIdSingle(ROOT, BlockEntity.class, "block_entity", -1) { 54 | @Override 55 | public BlockEntity readContext(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 56 | ActiveMinecraftConnection mcConn = (ActiveMinecraftConnection) ctx.getConnection(); 57 | PlayerEntity player = mcConn.getPlayer(); 58 | return player.getWorld().getBlockEntity(buffer.readBlockPos()); 59 | } 60 | 61 | @Override 62 | public void writeContext(NetByteBuf buffer, IMsgWriteCtx ctx, BlockEntity value) { 63 | buffer.writeBlockPos(value.getPos()); 64 | 65 | if (ctx instanceof MessageContext.Write wCtx) { 66 | wCtx.__debugBlockEntity = value; 67 | } 68 | } 69 | }; 70 | ENTITY = new ParentNetIdSingle(ROOT, Entity.class, "entity", Integer.BYTES) { 71 | @Override 72 | public Entity readContext(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 73 | ActiveMinecraftConnection mcConn = (ActiveMinecraftConnection) ctx.getConnection(); 74 | PlayerEntity player = mcConn.getPlayer(); 75 | return player.getWorld().getEntityById(buffer.readInt()); 76 | } 77 | 78 | @Override 79 | public void writeContext(NetByteBuf buffer, IMsgWriteCtx ctx, Entity value) { 80 | buffer.writeInt(value.getId()); 81 | } 82 | }; 83 | // We're not changing the internal name, because that would needlessly break compat 84 | SCREEN_HANDLER = new ParentNetIdSingle(ROOT, ScreenHandler.class, "container", Integer.BYTES) { 85 | @Override 86 | public ScreenHandler readContext(NetByteBuf buffer, IMsgReadCtx ctx) throws InvalidInputDataException { 87 | ActiveMinecraftConnection mcConn = (ActiveMinecraftConnection) ctx.getConnection(); 88 | PlayerEntity player = mcConn.getPlayer(); 89 | int syncId = buffer.readInt(); 90 | ScreenHandler currentContainer = player.currentScreenHandler; 91 | if (currentContainer == null || currentContainer.syncId != syncId) { 92 | return null; 93 | } 94 | return currentContainer; 95 | } 96 | 97 | @Override 98 | public void writeContext(NetByteBuf buffer, IMsgWriteCtx ctx, ScreenHandler value) { 99 | buffer.writeInt(value.syncId); 100 | } 101 | }; 102 | CONTAINER = SCREEN_HANDLER; 103 | 104 | CACHE_ITEMS_WITHOUT_AMOUNT 105 | = new NetObjectCache<>(ROOT.child("cache_item_stack"), new Hash.Strategy() { 106 | 107 | @Override 108 | public int hashCode(ItemStack o) { 109 | return Objects.hash(new Object[] { o.getItem(), o.getNbt() }); 110 | } 111 | 112 | @Override 113 | public boolean equals(ItemStack a, ItemStack b) { 114 | if (a == null || b == null) { 115 | return a == b; 116 | } 117 | if (a.getItem() != b.getItem()) { 118 | return false; 119 | } 120 | NbtCompound tagA = a.getNbt(); 121 | NbtCompound tagB = b.getNbt(); 122 | if (tagA == null || tagB == null) { 123 | return tagA == tagB; 124 | } 125 | return tagA.equals(tagB); 126 | } 127 | 128 | }, new IEntrySerialiser() { 129 | 130 | @Override 131 | public void write(ItemStack obj, ActiveConnection connection, NetByteBuf buffer) { 132 | if (obj.isEmpty()) { 133 | buffer.writeBoolean(false); 134 | } else { 135 | buffer.writeBoolean(true); 136 | Item item_1 = obj.getItem(); 137 | buffer.writeVarInt(Item.getRawId(item_1)); 138 | NbtCompound tag = null; 139 | if (item_1.isDamageable() || item_1.isNbtSynced()) { 140 | tag = obj.getNbt(); 141 | } 142 | buffer.writeNbt(tag); 143 | } 144 | } 145 | 146 | @Override 147 | public ItemStack read(ActiveConnection connection, NetByteBuf buffer) { 148 | if (!buffer.readBoolean()) { 149 | return ItemStack.EMPTY; 150 | } else { 151 | int id = buffer.readVarInt(); 152 | ItemStack stack = new ItemStack(Item.byRawId(id)); 153 | stack.setNbt(buffer.readNbt()); 154 | return stack; 155 | } 156 | } 157 | }); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/impl/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | /** 9 | *

LibNetworkStack's minecraft-specific library.

10 | *

11 | * First off: this is based fully on the concepts documented in the {@link alexiil.mc.lib.net main library}, but with a 12 | * few things different: 13 | *

    14 | *
  1. Every {@link alexiil.mc.lib.net.TreeNetIdBase packet} must be have 15 | * {@link alexiil.mc.lib.net.impl.McNetworkStack#ROOT} as it's root parent network ID - otherwise it will crash when you 16 | * try to send it
  2. 17 | *
  3. All network connections are {@link alexiil.mc.lib.net.BufferedConnection buffered} until the end of every tick - 18 | * so you cannot expect them to arrive before normal minecraft packets, unless you explicitly call 19 | * {@link alexiil.mc.lib.net.NetIdBase#notBuffered()} on the network ID.
  4. 20 | *
21 | */ 22 | package alexiil.mc.lib.net.impl; 23 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/api/IBlockEntityInitialData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.api; 9 | 10 | import net.minecraft.block.entity.BlockEntity; 11 | import net.minecraft.server.network.ServerPlayerEntity; 12 | 13 | import alexiil.mc.lib.net.impl.BlockEntityInitialData; 14 | 15 | /** This should be implemented by {@link BlockEntity}s that wish to send their own initial data packet to players 16 | * individually, rather than using {@link BlockEntity#toInitialChunkDataTag()} or {@link BlockEntity#toUpdatePacket()}. 17 | * 18 | * @deprecated This has been renamed to {@link BlockEntityInitialData}, and moved out of this mixin package and into the 19 | * "impl" package. (Although implementing this still works). */ 20 | @Deprecated 21 | public interface IBlockEntityInitialData extends BlockEntityInitialData { 22 | @Override 23 | void sendInitialData(ServerPlayerEntity to); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/api/INetworkStateMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.api; 9 | 10 | import java.util.function.Function; 11 | 12 | import net.minecraft.network.NetworkSide; 13 | 14 | import alexiil.mc.lib.net.impl.IPacketCustomId; 15 | import net.minecraft.network.PacketByteBuf; 16 | 17 | public interface INetworkStateMixin { 18 |

> int libnetworkstack_registerPacket(NetworkSide recvSide, Class

klass, Function factory); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/api/IPacketHandlerMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.api; 9 | 10 | import java.util.function.Function; 11 | 12 | import net.minecraft.network.PacketByteBuf; 13 | import net.minecraft.network.packet.Packet; 14 | 15 | public interface IPacketHandlerMixin { 16 | int libnetworkstack_register(Class> klass, Function> factory); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/api/IThreadedAnvilChunkStorageMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.api; 9 | 10 | import net.minecraft.block.entity.BlockEntity; 11 | import net.minecraft.server.world.ChunkHolder; 12 | import net.minecraft.util.math.ChunkPos; 13 | 14 | import alexiil.mc.lib.net.InternalMsgUtil; 15 | 16 | /** Used by {@link InternalMsgUtil}.createAndSendDebugThrowable to debug {@link BlockEntity} datas sent before their 17 | * chunks are sent. */ 18 | public interface IThreadedAnvilChunkStorageMixin { 19 | ChunkHolder libnetworkstack_getChunkHolder(ChunkPos chunkPos); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/api/ITickCounterMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.api; 9 | 10 | public interface ITickCounterMixin { 11 | long libnetworkstack__getPrevTimeMillis(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/api/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | /** Mixin APIs. Only {@link alexiil.mc.lib.net.mixin.api.IBlockEntityInitialData} is publicly supported. */ 9 | package alexiil.mc.lib.net.mixin.api; 10 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/impl/ChunkHolderMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.impl; 9 | 10 | import java.util.List; 11 | 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 17 | 18 | import net.minecraft.block.entity.BlockEntity; 19 | import net.minecraft.server.network.ServerPlayerEntity; 20 | import net.minecraft.server.world.ChunkHolder; 21 | import net.minecraft.util.math.BlockPos; 22 | import net.minecraft.world.World; 23 | 24 | import alexiil.mc.lib.net.impl.BlockEntityInitialData; 25 | 26 | @Mixin(ChunkHolder.class) 27 | public abstract class ChunkHolderMixin { 28 | 29 | private static final String LIST = "Ljava/util/List;"; 30 | private static final String WORLD = "Lnet/minecraft/world/World;"; 31 | private static final String BLOCK_POS = "Lnet/minecraft/util/math/BlockPos;"; 32 | 33 | @Inject(at = @At("RETURN"), method = "sendBlockEntityUpdatePacket(" + LIST + WORLD + BLOCK_POS + ")V", 34 | locals = LocalCapture.CAPTURE_FAILHARD) 35 | void sendCustomUpdatePacket(List players, World world, BlockPos pos, CallbackInfo ci, 36 | BlockEntity be) { 37 | 38 | if (be instanceof BlockEntityInitialData dataBe) { 39 | for (ServerPlayerEntity player : players) { 40 | dataBe.sendInitialData(player); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/impl/MinecraftClientMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.impl; 9 | 10 | import org.spongepowered.asm.mixin.Mixin; 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 | import net.minecraft.client.MinecraftClient; 17 | import net.minecraft.client.render.RenderTickCounter; 18 | 19 | import alexiil.mc.lib.net.impl.ActiveClientConnection; 20 | import alexiil.mc.lib.net.impl.CoreMinecraftNetUtil; 21 | import alexiil.mc.lib.net.mixin.api.ITickCounterMixin; 22 | 23 | @Mixin(value = MinecraftClient.class) 24 | public class MinecraftClientMixin { 25 | 26 | @Shadow 27 | private RenderTickCounter renderTickCounter; 28 | 29 | @Inject(at = @At(value = "CONSTANT", args = { "stringValue=scheduledExecutables" }), method = "render(Z)V") 30 | private void afterIncrementTickCounter(CallbackInfo ci) { 31 | ActiveClientConnection acc = CoreMinecraftNetUtil.getCurrentClientConnection(); 32 | if (acc != null) { 33 | acc.onIncrementMinecraftTickCounter( 34 | ((ITickCounterMixin) renderTickCounter).libnetworkstack__getPrevTimeMillis() 35 | ); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/impl/NetworkStateMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.impl; 9 | 10 | import java.util.Map; 11 | import java.util.function.Function; 12 | 13 | import alexiil.mc.lib.net.mixin.api.IPacketHandlerMixin; 14 | 15 | import net.minecraft.network.PacketByteBuf; 16 | import org.spongepowered.asm.mixin.Final; 17 | import org.spongepowered.asm.mixin.Mixin; 18 | import org.spongepowered.asm.mixin.Shadow; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 22 | 23 | import net.minecraft.network.NetworkSide; 24 | import net.minecraft.network.NetworkState; 25 | import net.minecraft.network.packet.Packet; 26 | 27 | import alexiil.mc.lib.net.impl.IPacketCustomId; 28 | import alexiil.mc.lib.net.mixin.api.INetworkStateMixin; 29 | 30 | @Mixin(NetworkState.class) 31 | public class NetworkStateMixin implements INetworkStateMixin { 32 | 33 | @Final 34 | @Shadow 35 | private static Map>, NetworkState> HANDLER_STATE_MAP; 36 | 37 | @Final 38 | @Shadow 39 | private Map packetHandlers; 40 | 41 | @Override 42 | public

> int libnetworkstack_registerPacket(NetworkSide recvSide, Class

klass, Function factory) { 43 | IPacketHandlerMixin mapping = packetHandlers.get(recvSide); 44 | int id = mapping.libnetworkstack_register(klass, factory); 45 | HANDLER_STATE_MAP.put(klass, (NetworkState) (Object) this); 46 | return id; 47 | } 48 | 49 | @Inject( 50 | at = @At("HEAD"), 51 | method = "getPacketId", 52 | cancellable = true 53 | ) 54 | private void getPacketId(NetworkSide side, Packet pkt, CallbackInfoReturnable ci) throws Exception { 55 | if (pkt instanceof IPacketCustomId) { 56 | ci.setReturnValue(((IPacketCustomId) pkt).getReadId()); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/impl/PacketHandlerMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.impl; 9 | 10 | import java.util.List; 11 | import java.util.function.Function; 12 | 13 | import net.minecraft.network.PacketByteBuf; 14 | import org.apache.logging.log4j.LogManager; 15 | import org.spongepowered.asm.mixin.Final; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Shadow; 18 | 19 | import net.minecraft.network.listener.PacketListener; 20 | import net.minecraft.network.packet.Packet; 21 | 22 | import alexiil.mc.lib.net.mixin.api.IPacketHandlerMixin; 23 | 24 | import it.unimi.dsi.fastutil.objects.Object2IntMap; 25 | 26 | @Mixin(targets = "net.minecraft.network.NetworkState$PacketHandler") 27 | abstract class PacketHandlerMixin implements IPacketHandlerMixin { 28 | 29 | @Final 30 | @Shadow 31 | private Object2IntMap>> packetIds; 32 | 33 | @Final 34 | @Shadow 35 | private List>> packetFactories; 36 | 37 | @Override 38 | public int libnetworkstack_register(Class> klass, Function> factory) { 39 | int int_1 = this.packetFactories.size(); 40 | int int_2 = this.packetIds.put((Class>) klass, int_1); 41 | if (int_2 != -1) { 42 | String string_1 = "Packet " + klass + " is already registered to ID " + int_2; 43 | LogManager.getLogger().fatal(string_1); 44 | throw new IllegalArgumentException(string_1); 45 | } else { 46 | this.packetFactories.add((Function>) factory); 47 | } 48 | 49 | return int_1; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/impl/RenderTickCounterMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.impl; 9 | 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | 13 | import net.minecraft.client.render.RenderTickCounter; 14 | 15 | import alexiil.mc.lib.net.mixin.api.ITickCounterMixin; 16 | 17 | @Mixin(RenderTickCounter.class) 18 | public class RenderTickCounterMixin implements ITickCounterMixin { 19 | 20 | @Shadow 21 | private long prevTimeMillis; 22 | 23 | @Override 24 | public long libnetworkstack__getPrevTimeMillis() { 25 | return prevTimeMillis; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/impl/ServerPlayNetworkHandlerAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.impl; 9 | 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.gen.Accessor; 12 | 13 | import net.minecraft.server.MinecraftServer; 14 | import net.minecraft.server.network.ServerPlayNetworkHandler; 15 | 16 | @Mixin(ServerPlayNetworkHandler.class) 17 | public interface ServerPlayNetworkHandlerAccessor { 18 | 19 | @Accessor("server") 20 | MinecraftServer libnetworkstack_getServer(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/mixin/impl/ThreadedAnvilChunkStorageMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.mixin.impl; 9 | 10 | import org.apache.commons.lang3.mutable.MutableObject; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | import net.minecraft.block.entity.BlockEntity; 18 | import net.minecraft.server.network.ServerPlayerEntity; 19 | import net.minecraft.server.world.ChunkHolder; 20 | import net.minecraft.server.world.ThreadedAnvilChunkStorage; 21 | import net.minecraft.util.math.ChunkPos; 22 | import net.minecraft.world.chunk.WorldChunk; 23 | 24 | import alexiil.mc.lib.net.impl.BlockEntityInitialData; 25 | import alexiil.mc.lib.net.mixin.api.IThreadedAnvilChunkStorageMixin; 26 | 27 | @Mixin(ThreadedAnvilChunkStorage.class) 28 | public abstract class ThreadedAnvilChunkStorageMixin implements IThreadedAnvilChunkStorageMixin { 29 | 30 | @Inject(at = @At("RETURN"), method = "sendChunkDataPackets(Lnet/minecraft/server/network/ServerPlayerEntity;" 31 | + "Lorg/apache/commons/lang3/mutable/MutableObject;Lnet/minecraft/world/chunk/WorldChunk;)V") 32 | private void libnetworkstack_postSendPackets( 33 | ServerPlayerEntity player, MutableObject packet, WorldChunk chunk, CallbackInfo ci 34 | ) { 35 | for (BlockEntity be : chunk.getBlockEntities().values()) { 36 | if (be instanceof BlockEntityInitialData) { 37 | ((BlockEntityInitialData) be).sendInitialData(player); 38 | } 39 | } 40 | } 41 | 42 | @Shadow 43 | private ChunkHolder getChunkHolder(long pos) { 44 | throw new Error("Shadow mixin failed to apply!"); 45 | } 46 | 47 | @Override 48 | public ChunkHolder libnetworkstack_getChunkHolder(ChunkPos chunkPos) { 49 | return getChunkHolder(chunkPos.toLong()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/alexiil/mc/lib/net/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | /** 9 | *

LibNetworkStack's base library.

10 | *

11 | * First off: this isn't completely designed around minecraft, so if you are only interested in using this in a normal 12 | * minecraft networking you probably want to read the javadoc for the {@link alexiil.mc.lib.net.impl} package as well. 13 | *

14 | * There are a few main concepts: 15 | *

    16 | *
  1. Network ID organisation
  2. 17 | *
  3. Connections
  4. 18 | *
  5. Contextual parent network ID's
  6. 19 | *
  7. Dynamic parent network ID's
  8. 20 | *
  9. Data network ID's
  10. 21 | *
  11. Signal network ID's
  12. 22 | *
  13. Caches
  14. 23 | *
  15. Debugging
  16. 24 | *
25 | *

Network ID organisation

26 | *

27 | * Network ID's are organised into a tree: the root node is always a {@link alexiil.mc.lib.net.ParentNetId ParentNetId}, 28 | * and the child nodes can either extend {@link alexiil.mc.lib.net.ParentNetIdBase ParentNetIdBase} (for organising 29 | * nodes), or {@link alexiil.mc.lib.net.NetIdBase NetIdBase} (for sending data).
30 | * Every ID has a name, usually in the form "mod_id:mod_specific_name", however it is only important that the name not 31 | * collide with other children of the same parent. These names are used to assign integer ID's to each network ID so 32 | * that the full name doesn't have to be sent with each and every packet, so they must be the same at both ends! 33 | *

34 | *

Connections

35 | *

36 | * Each individual connection has it's own {@link alexiil.mc.lib.net.ActiveConnection ActiveConnection} object 37 | * associated with it: this stores the current network ID registry, any caches, and handles reading and writing packets 38 | * out to the underlying connection. 39 | *

40 | * There is a second base type: a {@link alexiil.mc.lib.net.BufferedConnection BufferedConnection}, which will buffer 41 | * (or queue) packets until it's next "tick". This is generally a large performance optimisation if you need to send a 42 | * lot of smaller packets. 43 | *

44 | *

Contextual parent network ID's

45 | *

46 | * In addition to organising all network nodes, any parent type that extends {@link alexiil.mc.lib.net.ParentNetIdSingle 47 | * ParentNetIdSingle} can also write out data for it's represented object. For example you might have a network ID for a 48 | * BlockEntity, and then several children of that block entity that all need to communicate with the same entity on the 49 | * other side. 50 | *

51 | *

Dynamic parent network ID's

52 | *

53 | * It is occasionally useful for an object to be contained within multiple different types of object, for example a 54 | * buildcraft combustion engine might be contained within a block entity, or used in a flying robot. Dynamic network 55 | * ID's allows the same object to communicate even if it doesn't necessarily have a single path upwards to the root. 56 | *

57 | * Unlike normal ID's the dynamic parent must know the child that it will be sending, rather than the child needing to 58 | * know the parent. 59 | *

60 | * (List title): 61 | * 62 | * 63 | * 64 | * 65 | * 66 | * 67 | * 68 | * 69 | * 70 | * 71 | * 72 | * 73 | * 75 | * 76 | * 77 | * 78 | * 79 | * 80 | * 81 | *
ClassOverview
{@link alexiil.mc.lib.net.ParentDynamicNetId ParentDynamicNetId}The parent for a specific {@link alexiil.mc.lib.net.DynamicNetId}
{@link alexiil.mc.lib.net.DynamicNetId DynamidNetId}The child which doesn't know what is it's parent, but the object itself must have some way to obtain it's current 74 | * parent is via {@link alexiil.mc.lib.net.DynamicNetId.LinkAccessor#getLink(Object) LinkAccessor#getLink(Object)}
{@link alexiil.mc.lib.net.DynamicNetLink DynamicNetLink}The per-instance link object that holds both the parent and the child network ID and objects.
82 | *

83 | *

Data network ID's

84 | *

85 | * These allow you to send and receive between 1 and 65,536 bytes of binary data. If you know the exact length of the 86 | * data to be sent you can pre-specify it in the constructor, or you can allow using a shorter (or longer) maximum 87 | * packet length by calling either {@link alexiil.mc.lib.net.NetIdBase#setTinySize() setTinySize()} or 88 | * {@link alexiil.mc.lib.net.NetIdBase#setLargeSize() setLargeSize()}. 89 | *

90 | * Data ID's come in two variants: either untyped (which are somewhat comparable to the "static" java modifier for 91 | * methods), or typed, which require (and will deserialise) an object for them to be called upon. 92 | *

93 | * 94 | * 95 | * 96 | * 97 | * 98 | * 99 | * 100 | * 101 | * 102 | * 103 | * 104 | * 105 | * 106 | * 107 | * 108 | * 109 | *
ParentClass
Untyped{@link alexiil.mc.lib.net.ParentNetId ParentNetId}{@link alexiil.mc.lib.net.NetIdData NetIdData}
Typed{@link alexiil.mc.lib.net.ParentNetIdSingle ParentNetIdSingle}{@link alexiil.mc.lib.net.NetIdDataK NetIdDataK}
110 | *

111 | *

Signal network ID's

112 | *

113 | * These are very similar to data network ID's, except they won't write any data out: instead the mere presence of this 114 | * packet should convey all of the information required. Two variants exist: typed and untyped depending on whether the 115 | * parent has a contextual object or not. 116 | * 117 | * 118 | * 119 | * 120 | * 121 | * 122 | * 123 | * 124 | * 125 | * 126 | * 127 | * 128 | * 129 | * 130 | * 131 | * 132 | *
ParentClass
Untyped{@link alexiil.mc.lib.net.ParentNetId ParentNetId}{@link alexiil.mc.lib.net.NetIdSignal NetIdSignal}
Typed{@link alexiil.mc.lib.net.ParentNetIdSingle ParentNetIdSingle}{@link alexiil.mc.lib.net.NetIdSignalK NetIdSignalK}
133 | *

134 | *

Caches

135 | *

136 | * These are a slightly more niche feature, but still useful nonetheless. Essentially these offer a way to create a 137 | * registry of objects to/from integer ID's on a per-connection, per-use basis. This can help avoid a situation where 138 | * you need to reliably sync a (possibly large) registry of id's right at the start of a network connection, especially 139 | * if not all of those ID's will be used over it's duration. 140 | *

141 | * The only class associated with caches is {@link alexiil.mc.lib.net.NetObjectCache NetObjectCache}, and it's usage is 142 | * simple: Just create a new {@link alexiil.mc.lib.net.ParentNetId ParentNetId}, then pass in a hash/equals and network 143 | * serialiser implementation. (Alternatively if your data can be mapped to/from an {@link net.minecraft.util.Identifier} 144 | * then you can use the function based factory: 145 | * {@link alexiil.mc.lib.net.NetObjectCache#createMappedIdentifier(ParentNetId, java.util.function.Function, java.util.function.Function) 146 | * createMappedIdentifier}). 147 | *

148 | * To send an ID you can then call {@link alexiil.mc.lib.net.NetObjectCache#getId(ActiveConnection, Object) getId} to 149 | * get the ID that will be sent. (Note that the ID's *are* buffered by default, so you must only use them in a buffered 150 | * connection, or call {@link alexiil.mc.lib.net.NetObjectCache #notBuffered()}). 151 | *

152 | * On the receiving end you can call {@link alexiil.mc.lib.net.NetObjectCache#getObj(ActiveConnection, int)} to obtain 153 | * the original object (although it will return null if the entry isn't present - either if it hasn't sent yet or you 154 | * passed it the wrong ID. 155 | *

156 | *

Debugging

157 | *

158 | * To enable debugging (also known as log spam) you can add the option "-Dlibnetworkstack.debug=true" to the launch 159 | * arguments. (You can also debug caches separately with "-Dlibnetworkstack.cache.debug=true"). */ 160 | package alexiil.mc.lib.net; 161 | -------------------------------------------------------------------------------- /src/main/resources/assets/libnetworkstack/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexIIL/LibNetworkStack/6fe7c8755cbd5bab03a7ccab0a1ab59a94b57851/src/main/resources/assets/libnetworkstack/icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/libnetworkstack/icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexIIL/LibNetworkStack/6fe7c8755cbd5bab03a7ccab0a1ab59a94b57851/src/main/resources/assets/libnetworkstack/icon_1024.png -------------------------------------------------------------------------------- /src/main/resources/changelog/0.1.0.txt: -------------------------------------------------------------------------------- 1 | First release. 2 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.1.1.txt: -------------------------------------------------------------------------------- 1 | Bug Fixes: 2 | 3 | * Added missing "throws InvalidInputDataException" to some net object cache read methods. 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.10.0.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Updated to Minecraft 1.20 4 | * Fixed bug in debug markers 5 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.2.1.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Added MsgUtil.ensureEmpty, which is now called after every 4 | packet is received (provided it isn't dropped). 5 | * If the packet hasn't been fully read then this will throw 6 | an exception if debugging is enabled, or log an error if not. 7 | * Added IMsgReadCtx.drop, which will log a message in debug mode, 8 | and also prevent the message supplier from ensuring that the 9 | buffer has been fully read. 10 | * Improved debug messages a tiny bit. 11 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.3.0.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Ported to minecraft 1.15.1 (mostly thanks to laura for the port). 4 | * Added overrides in NetByteBuf.(read/write)Var(Int/Long) that handle 5 | negative numbers correctly. (So -7 will write out 1 byte rather than 5). 6 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.3.4.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Updated to minecraft 1.15.2. 4 | * Added NetIdBase.withTinySize, withNormalSize, withLargeSize, withoutBuffering 5 | * These are equivalent to setTinySize (etc) but return the NetIdBase object itself. 6 | * Implemented NetIdBase.notBuffered() - now it actually does something! 7 | * Also allowed the buffering flag to be changed at any time - as it doesn't need to 8 | be stable over time. 9 | * Added NetIdBase.toClientOnly, toServerOnly, and toEitherSide: 10 | * These check to see if they are being sent from the correct side when sending them 11 | and check when they are received. 12 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.3.6.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Updated the MinecraftClient mixin to support both 1.15.2 and 20w06a. 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.4.0.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Updated to 1.16-pre.2. 4 | * Allowed this version to be loaded in any version of 1.16. 5 | * Print more debugging information about a packet if it failed to read correctly. 6 | * If "debug" or "debug.request_types" is enabled then this will include a packet payload breakdown. 7 | * Added a config, with two options: 8 | * "debug": an alternative for "-Dlibnetworkstack.debug=true" 9 | * "debug.request_types": requests debugging type information from the other side. 10 | * Added a new interface: "IBlockEntityInitialData", which can be used to send custom 11 | data to each player instead of vanilla's "toInitialChunkDataTag()", and "toUpdatePacket()". 12 | (Although this is mostly useful for BE's that use LNS exclusively for networking). 13 | * Exceptions thrown while reading a packet will now cause the network 14 | connection to be disconnected, as minecraft used to swallow any problems. 15 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.4.1.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Removed the "debug.all" option and replaced it with "debug.log". 4 | * This no longer forces every other debug option to be turned on when set to true. 5 | * Added a debug option to print the stacktrace of the sender when reading throws an exception. 6 | * Unlike types this must be enabled on both sides for it to work. 7 | * This is controlled with the option "debug.record_stacktraces" 8 | * Added a simpler cache type (NetObjectCacheSimple) 9 | * Unlike the full cache, this writes cache data out in-place, rather than requiring a separate network ID. 10 | * While this makes it simpler to create caches this writes an extra boolean. 11 | * Added markers, which are shown in the debug log if "debug.record_types" is enabled. 12 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.4.2.txt: -------------------------------------------------------------------------------- 1 | Bug fixes: 2 | 3 | * Fixed debug.record_stacktraces being *very* buggy when it's sent alongside other packets. 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.4.3.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Added "impl.BlockEntityInitialData", which is the new version of the now-deprecated "mixin.api.IBlockEntityInitialData". 4 | 5 | Bug fixes: 6 | 7 | * Fixed a crash with fabric networking v1. 8 | * This deprecates all usage of "PacketContext" in ActiveConnections, adding getPlayer() instead. 9 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.4.4.txt: -------------------------------------------------------------------------------- 1 | Bug fixes: 2 | 3 | * Fixed a second crash with fabric networking v1. 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.4.7.txt: -------------------------------------------------------------------------------- 1 | Bug fixes: 2 | 3 | * [#5] Fixed sometimes sending packet data too early. 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.4.8.txt: -------------------------------------------------------------------------------- 1 | Bug fixes: 2 | 3 | * Fixed using only partial-bytes in both the header and payload of a NetIdDataK would result in the packet being dropped. 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.5.0.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Updated to minecraft 1.17-rc1 (Kneelawk) 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.6.0.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Updated to Minecraft 1.18 release candidate 3 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.6.3.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Bumped Minecraft requirement to 1.18.2. 4 | * Added "passthrough" mode to "NetByteBuf", which is useful for using "CheckingNetByteBuf"s for regular minecraft packets. 5 | * Added initial backend support for "NetworkDrainCleaner", a work-in-progress full network debugging tool based on LNS. 6 | * Added "BlockEntityInitialData.getPlayersWatching( [BlockEntity] | [ServerWorld, BlockPos] )", which is used to workaround BlockEntity data getting sent too early. 7 | * Changed InternalMsgUtil.wrapFullPayload to always check for packets that are too long even when "-ea" isn't specified as a JVM option. 8 | * Changed debug types to use a "LARGE" packet size (3 bytes for length) rather than "NORMAL" (2 bytes) 9 | 10 | Bugfixes: 11 | 12 | * Fixed InternalMsgUtil.onReceive not catching netty's "CodecException" as well. 13 | * Fixed InternalMsgUtil.onReceive not allowing the usage of "ByteBuf.mark()" inside packet reading / writing 14 | * Fixed NetIdBase.set[Tiny|Normal|Large]Size() methods not working. 15 | * Improved CoreMinecraftNetUtils handling of null or invalid connections. 16 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.7.0.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Updated to Minecraft 1.19 (Kneelawk) 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.7.1.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Fixes client classes being loaded on dedicated servers. 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.7.2.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Make BufferedConnection.flushQueue public, and fix the documentation for NetIdBase#withoutBuffering to be correct. 4 | (Packet write order is always kept) 5 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.8.0.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Updated to Minecraft 1.19.3 (PTOM76) 4 | -------------------------------------------------------------------------------- /src/main/resources/changelog/0.9.0.txt: -------------------------------------------------------------------------------- 1 | Changes: 2 | 3 | * Updated to Minecraft 1.19.4 4 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "libnetworkstack", 4 | "name": "Lib Network Stack", 5 | "description": "Some tooling for managing network ID's, and a few other things.", 6 | "version": "$version", 7 | "environment": "*", 8 | "entrypoints": { 9 | "main": [ 10 | "alexiil.mc.lib.net.LibNetworkStack" 11 | ], 12 | "client": [ 13 | "alexiil.mc.lib.net.LibNetworkStackClient" 14 | ] 15 | }, 16 | "depends": { 17 | "minecraft": [ ">=1.20- <1.21-" ], 18 | "fabricloader": ">=0.4.0", 19 | "fabric": "*" 20 | }, 21 | "mixins": [ 22 | "libnetworkstack.client.json", 23 | "libnetworkstack.common.json" 24 | ], 25 | "custom": { 26 | "modmenu": { 27 | "badges": ["library"] 28 | } 29 | }, 30 | "icon": "assets/libnetworkstack/icon.png", 31 | "authors": [ "AlexIIL" ], 32 | "license": "Mozilla Public License Version 2.0", 33 | "__buildscript_diff": { "base": {} } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/libnetworkstack.client.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "alexiil.mc.lib.net.mixin.impl", 4 | "compatibilityLevel": "JAVA_8", 5 | "client": [ 6 | "MinecraftClientMixin", 7 | "RenderTickCounterMixin" 8 | ], 9 | "injectors": { 10 | "defaultRequire": 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/libnetworkstack.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "alexiil.mc.lib.net.mixin.impl", 4 | "compatibilityLevel": "JAVA_8", 5 | "mixins": [ 6 | "ChunkHolderMixin", 7 | "NetworkStateMixin", 8 | "PacketHandlerMixin", 9 | "ServerPlayNetworkHandlerAccessor", 10 | "ThreadedAnvilChunkStorageMixin" 11 | ], 12 | "injectors": { 13 | "defaultRequire": 1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/alexiil/mc/lib/net/test/McBufferTester.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 AlexIIL 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | package alexiil.mc.lib.net.test; 9 | 10 | import java.util.Random; 11 | 12 | import org.junit.Assert; 13 | import org.junit.Test; 14 | 15 | import alexiil.mc.lib.net.NetByteBuf; 16 | 17 | public class McBufferTester { 18 | 19 | @Test 20 | public void testNegativePacked() { 21 | NetByteBuf buf = NetByteBuf.buffer(); 22 | Assert.assertEquals(0, buf.writerIndex()); 23 | buf.writeVarInt(-7); 24 | Assert.assertEquals(1, buf.writerIndex()); 25 | } 26 | 27 | @Test 28 | public void testVarInts() { 29 | Random rand = new Random(42); 30 | for (int i = 0; i < 0x1_000_000; i++) { 31 | NetByteBuf buf = NetByteBuf.buffer(); 32 | int val = rand.nextInt(); 33 | buf.writeVarInt(val); 34 | int got = buf.readVarInt(); 35 | if (got != val) { 36 | Assert.fail( 37 | "Expected \n<" + Integer.toBinaryString(val) + ">\nbut got \n<" + Integer.toBinaryString(got) + ">" 38 | ); 39 | } 40 | } 41 | 42 | for (int shift = 0; shift < 32; shift++) { 43 | int value = (1 << shift) - 1; 44 | NetByteBuf buf = NetByteBuf.buffer(); 45 | buf.writeVarInt(value); 46 | int used = buf.writerIndex(); 47 | System.out.println("writeVarInt(" + value + ") used " + used + (used == 1 ? " byte" : " bytes")); 48 | 49 | value = (1 << shift); 50 | buf = NetByteBuf.buffer(); 51 | buf.writeVarInt(value); 52 | used = buf.writerIndex(); 53 | System.out.println("writeVarInt(" + value + ") used " + used + (used == 1 ? " byte" : " bytes")); 54 | 55 | value = -(1 << shift); 56 | buf = NetByteBuf.buffer(); 57 | buf.writeVarInt(value); 58 | used = buf.writerIndex(); 59 | System.out.println("writeVarInt(" + value + ") used " + used + (used == 1 ? " byte" : " bytes")); 60 | 61 | value = -(1 << shift) - 1; 62 | buf = NetByteBuf.buffer(); 63 | buf.writeVarInt(value); 64 | used = buf.writerIndex(); 65 | System.out.println("writeVarInt(" + value + ") used " + used + (used == 1 ? " byte" : " bytes")); 66 | } 67 | } 68 | 69 | @Test 70 | public void testVarLongs() { 71 | Random rand = new Random(42); 72 | for (int i = 0; i < 0x1_000_000; i++) { 73 | NetByteBuf buf = NetByteBuf.buffer(); 74 | long val = rand.nextLong(); 75 | buf.writeVarLong(val); 76 | long got = buf.readVarLong(); 77 | if (got != val) { 78 | Assert.fail( 79 | "Expected \n<" + Long.toBinaryString(val) + ">\nbut got \n<" + Long.toBinaryString(got) + ">" 80 | ); 81 | } 82 | } 83 | } 84 | } 85 | --------------------------------------------------------------------------------