├── .github ├── FUNDING.yml └── workflows │ ├── gradle.yml │ └── publish.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── idea └── inspections.xml ├── settings.gradle └── src ├── .gitignore └── main ├── java ├── com │ └── google │ │ └── common │ │ └── collect │ │ ├── HydrogenEntrySet.java │ │ ├── HydrogenEntrySetIterator.java │ │ ├── HydrogenImmutableMapEntry.java │ │ └── HydrogenImmutableReferenceHashMap.java └── me │ └── jellysquid │ └── mods │ └── hydrogen │ ├── client │ ├── HydrogenClient.java │ ├── model │ │ └── HydrogenModelIdentifier.java │ └── resource │ │ ├── ModelCacheReloadListener.java │ │ └── ModelCaches.java │ ├── common │ ├── HydrogenMod.java │ ├── HydrogenPreLaunch.java │ ├── cache │ │ └── StatePropertyTableCache.java │ ├── collections │ │ ├── CollectionHelper.java │ │ ├── FastImmutableTable.java │ │ ├── FastImmutableTableCache.java │ │ ├── FixedArrayList.java │ │ └── ImmutablePairArrayList.java │ ├── dedup │ │ ├── DeduplicationCache.java │ │ └── IdentifierCaches.java │ ├── jvm │ │ ├── ClassConstructors.java │ │ └── ClassDefineTool.java │ ├── state │ │ ├── StatePropertyPredicateHelper.java │ │ ├── all │ │ │ ├── AllMatchOneBoolean.java │ │ │ └── AllMatchOneObject.java │ │ ├── any │ │ │ └── AllMatchAnyObject.java │ │ └── single │ │ │ ├── SingleMatchAny.java │ │ │ └── SingleMatchOne.java │ └── util │ │ ├── AllPredicate.java │ │ └── AnyPredicate.java │ └── mixin │ ├── chunk │ ├── MixinChunkSerializer.java │ └── MixinWorldChunk.java │ ├── client │ └── model │ │ ├── MixinBakedQuad.java │ │ ├── MixinBasicBakedModel.java │ │ ├── MixinModelIdentifier.java │ │ ├── MixinModelLoader.java │ │ ├── MixinMultipartBakedModel.java │ │ ├── MixinWeightedBakedModel.java │ │ └── json │ │ ├── MixinAndMultipartModelSelector.java │ │ ├── MixinOrMultipartModelSelector.java │ │ └── MixinSimpleMultipartModelSelector.java │ ├── nbt │ └── MixinNbtCompound.java │ ├── state │ └── MixinState.java │ └── util │ └── MixinIdentifier.java └── resources ├── assets └── hydrogen │ └── icon.png ├── fabric.mod.json └── hydrogen.mixins.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jellysquid3 4 | patreon: jellysquid 5 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI with Gradle 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up JDK 16 12 | uses: actions/setup-java@v1 13 | with: 14 | java-version: 16 15 | - name: Grant execute permission for gradlew 16 | run: chmod +x gradlew 17 | - name: Build with Gradle 18 | run: ./gradlew build 19 | - name: Upload build artifacts 20 | uses: actions/upload-artifact@v1 21 | with: 22 | name: build-artifacts 23 | path: build/libs -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release v2 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout sources 13 | uses: actions/checkout@v2 14 | - name: Set up JDK 16 15 | uses: actions/setup-java@v1 16 | with: 17 | java-version: 16 18 | - name: Grant execute permission for gradlew 19 | run: chmod +x gradlew 20 | - name: Upload assets to CurseForge 21 | run: ./gradlew build 22 | env: 23 | BUILD_RELEASE: ${{ github.event.prerelease == false }} 24 | - name: Upload assets to GitHub 25 | uses: AButler/upload-release-assets@v2.0 26 | with: 27 | files: 'build/libs/*;LICENSE' 28 | repo-token: ${{ secrets.GITHUB_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | run 4 | build 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Issues 2 | 3 | When opening issues, please be sure to include the following information as applicable. 4 | 5 | - The exact version of the mod you are running, such as `0.1.0-fabric`, and the version of Fabric/Forge you are using. 6 | - If your issue is a crash, attach the latest client or server log and the complete crash report as a file. You can 7 | attach these as a file (preferred) or host them on a service such as [GitHub Gist](https://gist.github.com/) or [Hastebin](https://hastebin.com/). 8 | - If your issue is a bug or otherwise unexpected behavior, explain what you expected to happen. 9 | - If your issue only occurs with other mods installed, be sure to specify the names and versions of those mods. 10 | 11 | ## Pull Requests 12 | 13 | It's super awesome to hear you're wishing to contribute to the project! Before you open a pull request, you'll need to 14 | give a quick read to the following guidelines. 15 | 16 | ### Contributor License Agreement (CLA) 17 | 18 | By submitting changes to this repository, you are hereby agreeing that: 19 | 20 | - Your contributions will be licensed irrecoverably under the [GNU LGPLv3](https://www.gnu.org/licenses/lgpl-3.0.html). 21 | - Your contributions are of your own work and free of legal restrictions (such as patents and copyrights) or other 22 | issues which would pose issues for inclusion or distribution under the above license. 23 | 24 | If you do not agree to these terms, please do not submit contributions to this repository. If you have any questions 25 | about these terms, feel free to get in contact with me through the [public Discord server](https://jellysquid.me/discord) or 26 | through opening an issue. 27 | 28 | ### Code Style 29 | 30 | When contributing source code changes to the project, ensure that you make consistent use of the code style guidelines 31 | used throughout the codebase (which follow pretty closely after the standard Java code style guidelines). These guidelines 32 | have also been packaged as EditorConfig and IDEA inspection profiles which can be found in the repository root and `idea` 33 | directory respectively. 34 | 35 | - Use 4 spaces for indentation, not tabs. Avoid lines which exceed 120 characters. 36 | - Use `this` to qualify member and field access. 37 | - Always use braces when writing if-statements and loops. 38 | - Annotate overriding methods with `@Override` so that breaking changes when updating will create hard compile errors. 39 | - Comment code which needs to mimic vanilla behavior with `[VanillaCopy]` so it can be inspected when updating. 40 | 41 | ### Making a Pull Request 42 | 43 | Your pull request should include a brief description of the changes it makes and link to any open issues which it 44 | resolves. You should also ensure that your code is well documented where non-trivial and that it follows the 45 | outlined code style guidelines above. 46 | 47 | If you're adding new Mixin patches to the project, please ensure that you have created appropriate entries to disable 48 | them in the config file. Mixins should always be self-contained and grouped into "patch sets" which are easy to isolate. 49 | 50 | Additionally, if you're making changes for the sake of improving performance in either the vanilla game or the project 51 | itself, try to provide a detailed test-case and benchmark for them. It's understandable that micro-benchmarking is 52 | difficult in the context of Minecraft, but even naive figures taken from a profiler, timings graph, or a simple counter 53 | will be greatly appreciated and help track incremental improvements. -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Status 2 | ##### January 23rd, 2022 3 | 4 | **This repository has been archived** as we do not have the time or resources needed to maintain the project. No further updates will be published, and 5 | we won't be providing help for using it. 6 | 7 | If you are looking for a replacement, consider taking a look at [FerriteCore](https://www.curseforge.com/minecraft/mc-mods/ferritecore-fabric), as it 8 | generally offers more significant improvements to memory usage in modded scenarios and is currently available for the latest version of Minecraft. 9 | 10 | --- 11 | 12 | ![Project icon](src/main/resources/assets/hydrogen/icon.png?raw=true) 13 | 14 | # Hydrogen (for Fabric) 15 | ![GitHub license](https://img.shields.io/github/license/jellysquid3/hydrogen-fabric.svg) 16 | ![GitHub issues](https://img.shields.io/github/issues/jellysquid3/hydrogen-fabric.svg) 17 | ![GitHub tag](https://img.shields.io/github/tag/jellysquid3/hydrogen-fabric.svg) 18 | 19 | Hydrogen is a free and open-source mod designed to reduce the game's memory requirements by implementing more 20 | memory-efficient data structures and logic. It's primarily designed for more heavily modded scenarios, but can 21 | offer (small) improvements even in lightly modded or vanilla scenarios. 22 | 23 | The mod works on both the **client and server**, and **doesn't require the mod to be installed 24 | on both sides**. However, the benefits of running Hydrogen on the server are pretty small as of the moment. 25 | 26 | #### Why are these patches not in [Lithium](https://github.com/jellysquid3/lithium-fabric)? 27 | 28 | Hydrogen relies on rather egregious hacks in order to load some code into the game, primarily because the Guava 29 | library developers really do not want people re-implementing their interfaces. These hacks are "safe" in the sense 30 | that the game will simply fail to start if a problem occurs, but are really rather not suitable for the standards 31 | which Lithium intends to promote. In other words, this mod could be looked at as "things too dirty to put in Lithium." 32 | 33 | ### Installation instructions 34 | 35 | Hydrogen relies on the [Fabric Loader](https://fabricmc.net/use). Users should select Fabric for either the Minecraft launcher (client) or 36 | the dedicated server (server) depending on their needs. 37 | Once you have installed Fabric, place the Hydrogen .jar in the `mods` folder generated by Fabric. 38 | 39 | ### Community 40 | 41 | If you'd like to get help with the mod, check out the latest developments, or be notified when there's a new release, 42 | the Discord community might be for you! You can join the official server for my mods by clicking 43 | [here](https://jellysquid.me/discord). 44 | 45 | ### Building from source 46 | 47 | If you're hacking on the code or would like to compile a custom build of Hydrogen from the latest sources, you'll want 48 | to start here. 49 | 50 | #### Prerequisites 51 | 52 | You will need to install JDK 8 in order to build Hydrogen. You can either install this through a package manager such as 53 | [Chocolatey](https://chocolatey.org/) on Windows or [SDKMAN!](https://sdkman.io/) on other platforms. If you'd prefer to 54 | not use a package manager, you can always grab the installers or packages directly from 55 | [AdoptOpenJDK](https://adoptopenjdk.net/). 56 | 57 | On Windows, the Oracle JDK/JRE builds should be avoided where possible due to their poor quality. Always prefer using 58 | the open-source builds from AdoptOpenJDK when possible. 59 | 60 | #### Compiling 61 | 62 | Navigate to the directory you've cloned this repository and launch a build with Gradle using `gradlew build` (Windows) 63 | or `./gradlew build` (macOS/Linux). If you are not using the Gradle wrapper, simply replace `gradlew` with `gradle` 64 | or the path to it. 65 | 66 | The initial setup may take a few minutes. After Gradle has finished building everything, you can find the resulting 67 | artifacts in `build/libs`. 68 | 69 | ### License 70 | 71 | Hydrogen is licensed under GNU LGPLv3, a free and open-source license. For more information, please see the 72 | [license file](https://github.com/jellysquid3/hydrogen-fabric/blob/1.16.x/dev/LICENSE.txt). 73 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '0.8-SNAPSHOT' 3 | } 4 | 5 | archivesBaseName = "${project.archives_base_name}-mc${project.minecraft_version}" 6 | group = project.maven_group 7 | version = project.mod_version 8 | 9 | def build_release = System.getenv("BUILD_RELEASE") == "true" 10 | def build_id = System.getenv("BUILD_ID") 11 | 12 | if (!build_release) { 13 | version += "-SNAPSHOT" 14 | } 15 | 16 | if (build_id != null) { 17 | version += "+build.${build_id}" 18 | } 19 | 20 | minecraft { 21 | refmapName = "mixins.hydrogen.refmap.json" 22 | } 23 | 24 | dependencies { 25 | //to change the versions see the gradle.properties file 26 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 27 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" 28 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 29 | 30 | include(modImplementation(fabricApi.module("fabric-resource-loader-v0", project.fabric_api_version))) 31 | } 32 | 33 | repositories { 34 | maven { url = "https://jitpack.io" } 35 | } 36 | 37 | if (project.use_third_party_mods) { 38 | repositories { 39 | maven { url = "https://maven.gegy.dev" } 40 | } 41 | 42 | dependencies { 43 | // DataBreaker applies a number of patches to eliminate the loading of data fixers 44 | // This greatly reduces the amount of time needed to start the game, but is generally unsafe in production 45 | modRuntime ("supercoder79:databreaker:${project.databreaker_version}") { 46 | transitive = false 47 | } 48 | } 49 | } 50 | 51 | processResources { 52 | inputs.property "version", project.version 53 | 54 | filesMatching("fabric.mod.json") { 55 | expand "version": project.version 56 | } 57 | } 58 | 59 | // ensure that the encoding is set to UTF-8, no matter what the system default is 60 | // this fixes some edge cases with special characters not displaying correctly 61 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 62 | tasks.withType(JavaCompile) { 63 | options.encoding = "UTF-8" 64 | } 65 | 66 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 67 | // if it is present. 68 | // If you remove this task, sources will not be generated. 69 | task sourcesJar(type: Jar, dependsOn: classes) { 70 | classifier = "sources" 71 | from sourceSets.main.allSource 72 | from "LICENSE" 73 | } 74 | 75 | jar { 76 | from "LICENSE.txt" 77 | } 78 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx1G 3 | 4 | # Fabric Properties 5 | # check these on https://modmuss50.me/fabric.html 6 | minecraft_version=1.17.1 7 | yarn_mappings=1.17.1+build.1 8 | loader_version=0.11.6 9 | fabric_api_version=0.36.1+1.17 10 | 11 | # Mod Properties 12 | mod_version=0.3 13 | maven_group=me.jellysquid.mods 14 | archives_base_name=hydrogen-fabric 15 | 16 | # If true, third-party mods will be loaded during runtime in the developer run configurations 17 | use_third_party_mods = true 18 | databreaker_version = 0.2.7 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaffeineMC/hydrogen-fabric/95da36046c2f55617a76ebb8ea1e1f2a330330e5/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-7.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or 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 UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /idea/inspections.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | jcenter() 4 | maven { 5 | name = 'Fabric' 6 | url = 'https://maven.fabricmc.net/' 7 | } 8 | gradlePluginPortal() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | jmh -------------------------------------------------------------------------------- /src/main/java/com/google/common/collect/HydrogenEntrySet.java: -------------------------------------------------------------------------------- 1 | package com.google.common.collect; 2 | 3 | import java.util.Map; 4 | 5 | class HydrogenEntrySet extends ImmutableSet> { 6 | private final K[] key; 7 | private final V[] value; 8 | 9 | private final int size; 10 | 11 | HydrogenEntrySet(K[] key, V[] value, int size) { 12 | this.key = key; 13 | this.value = value; 14 | this.size = size; 15 | } 16 | 17 | @Override 18 | public UnmodifiableIterator> iterator() { 19 | return new HydrogenEntrySetIterator<>(this.key, this.value, this.size); 20 | } 21 | 22 | @Override 23 | public boolean contains(Object object) { 24 | return false; 25 | } 26 | 27 | @Override 28 | boolean isPartialView() { 29 | return false; 30 | } 31 | 32 | @Override 33 | public int size() { 34 | return this.size; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/google/common/collect/HydrogenEntrySetIterator.java: -------------------------------------------------------------------------------- 1 | package com.google.common.collect; 2 | 3 | import java.util.Map; 4 | 5 | class HydrogenEntrySetIterator extends UnmodifiableIterator> { 6 | private final K[] key; 7 | private final V[] value; 8 | 9 | private int remaining; 10 | private int idx; 11 | 12 | public HydrogenEntrySetIterator(K[] key, V[] value, int remaining) { 13 | this.remaining = remaining; 14 | this.key = key; 15 | this.value = value; 16 | } 17 | 18 | @Override 19 | public boolean hasNext() { 20 | return this.remaining > 0; 21 | } 22 | 23 | @Override 24 | public Map.Entry next() { 25 | this.skipEmpty(); 26 | 27 | Map.Entry entry = new HydrogenImmutableMapEntry<>(this.key[this.idx], 28 | this.value[this.idx]); 29 | 30 | this.remaining--; 31 | this.idx++; 32 | 33 | return entry; 34 | } 35 | 36 | private void skipEmpty() { 37 | while (this.key[this.idx] == null) { 38 | this.idx++; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/google/common/collect/HydrogenImmutableMapEntry.java: -------------------------------------------------------------------------------- 1 | package com.google.common.collect; 2 | 3 | import java.util.Map; 4 | 5 | public class HydrogenImmutableMapEntry implements Map.Entry { 6 | private final K key; 7 | private final V value; 8 | 9 | public HydrogenImmutableMapEntry(K key, V value) { 10 | this.key = key; 11 | this.value = value; 12 | } 13 | 14 | @Override 15 | public K getKey() { 16 | return this.key; 17 | } 18 | 19 | @Override 20 | public V getValue() { 21 | return this.value; 22 | } 23 | 24 | @Override 25 | public V setValue(V value) { 26 | throw new UnsupportedOperationException(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/google/common/collect/HydrogenImmutableReferenceHashMap.java: -------------------------------------------------------------------------------- 1 | package com.google.common.collect; 2 | 3 | import it.unimi.dsi.fastutil.Hash; 4 | import it.unimi.dsi.fastutil.HashCommon; 5 | 6 | import java.util.Map; 7 | 8 | import static it.unimi.dsi.fastutil.HashCommon.arraySize; 9 | 10 | @SuppressWarnings("unused") 11 | public class HydrogenImmutableReferenceHashMap extends ImmutableMap { 12 | protected transient K[] key; 13 | protected transient V[] value; 14 | protected transient int mask; 15 | protected transient int size; 16 | 17 | public HydrogenImmutableReferenceHashMap() { 18 | 19 | } 20 | 21 | public HydrogenImmutableReferenceHashMap(Map map) { 22 | this(map.size(), Hash.DEFAULT_LOAD_FACTOR); 23 | 24 | for (Map.Entry entry : map.entrySet()) { 25 | this.putInternal(entry.getKey(), entry.getValue()); 26 | } 27 | } 28 | 29 | @SuppressWarnings("unchecked") 30 | private HydrogenImmutableReferenceHashMap(final int size, final float loadFactor) { 31 | if (loadFactor <= 0 || loadFactor > 1) { 32 | throw new IllegalArgumentException("Load factor must be greater than 0 and smaller than or equal to 1"); 33 | } 34 | 35 | if (size < 0) { 36 | throw new IllegalArgumentException("The expected number of elements must be nonnegative"); 37 | } 38 | 39 | int n = arraySize(size, loadFactor); 40 | 41 | this.key = (K[]) new Object[n]; 42 | this.value = (V[]) new Object[n]; 43 | this.mask = n - 1; 44 | this.size = size; 45 | } 46 | 47 | @Override 48 | public int size() { 49 | return this.size; 50 | } 51 | 52 | @Override 53 | public V get(final Object k) { 54 | int pos = HashCommon.mix(System.identityHashCode(k)) & this.mask; 55 | K curr = this.key[pos]; 56 | 57 | // The starting point. 58 | if (curr == null) { 59 | return null; 60 | } else if (k == curr) { 61 | return this.value[pos]; 62 | } 63 | 64 | // There's always an unused entry. 65 | while (true) { 66 | pos = pos + 1 & this.mask; 67 | curr = this.key[pos]; 68 | 69 | if (curr == null) { 70 | return null; 71 | } else if (k == curr) { 72 | return this.value[pos]; 73 | } 74 | } 75 | } 76 | 77 | private void putInternal(final K k, final V v) { 78 | final int pos = this.find(k); 79 | 80 | if (pos < 0) { 81 | int n = -pos - 1; 82 | 83 | this.key[n] = k; 84 | this.value[n] = v; 85 | } else { 86 | this.value[pos] = v; 87 | } 88 | } 89 | 90 | private int find(final K k) { 91 | int pos = HashCommon.mix(System.identityHashCode(k)) & this.mask; 92 | K curr = this.key[pos]; 93 | 94 | // The starting point. 95 | if (curr == null) { 96 | return -(pos + 1); 97 | } else if (k == curr) { 98 | return pos; 99 | } 100 | 101 | // There's always an unused entry. 102 | while (true) { 103 | pos = pos + 1 & this.mask; 104 | curr = this.key[pos]; 105 | 106 | if (curr == null) { 107 | return -(pos + 1); 108 | } else if (k == curr) { 109 | return pos; 110 | } 111 | } 112 | } 113 | 114 | @Override 115 | ImmutableSet> createEntrySet() { 116 | return new HydrogenEntrySet<>(this.key, this.value, this.size); 117 | } 118 | 119 | @Override 120 | boolean isPartialView() { 121 | return false; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/client/HydrogenClient.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.client; 2 | 3 | import me.jellysquid.mods.hydrogen.client.resource.ModelCacheReloadListener; 4 | import net.fabricmc.api.ClientModInitializer; 5 | import net.fabricmc.fabric.api.resource.ResourceManagerHelper; 6 | import net.minecraft.resource.ResourceType; 7 | 8 | public class HydrogenClient implements ClientModInitializer { 9 | @Override 10 | public void onInitializeClient() { 11 | ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES) 12 | .registerReloadListener(new ModelCacheReloadListener()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/client/model/HydrogenModelIdentifier.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.client.model; 2 | 3 | public interface HydrogenModelIdentifier { 4 | String[] getProperties(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/client/resource/ModelCacheReloadListener.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.client.resource; 2 | 3 | import com.google.common.collect.Lists; 4 | import me.jellysquid.mods.hydrogen.common.dedup.IdentifierCaches; 5 | import net.fabricmc.fabric.api.resource.ResourceReloadListenerKeys; 6 | import net.fabricmc.fabric.api.resource.SimpleResourceReloadListener; 7 | import net.minecraft.resource.ResourceManager; 8 | import net.minecraft.util.Identifier; 9 | import net.minecraft.util.profiler.Profiler; 10 | 11 | import java.util.Collection; 12 | import java.util.concurrent.CompletableFuture; 13 | import java.util.concurrent.Executor; 14 | 15 | public class ModelCacheReloadListener implements SimpleResourceReloadListener { 16 | @Override 17 | public CompletableFuture load(ResourceManager manager, Profiler profiler, Executor executor) { 18 | ModelCaches.cleanCaches(); 19 | 20 | return CompletableFuture.completedFuture(null); 21 | } 22 | 23 | @Override 24 | public CompletableFuture apply(Void data, ResourceManager manager, Profiler profiler, Executor executor) { 25 | ModelCaches.printDebug(); 26 | IdentifierCaches.printDebug(); 27 | 28 | return CompletableFuture.completedFuture(null); 29 | } 30 | 31 | @Override 32 | public Identifier getFabricId() { 33 | return new Identifier("hydrogen", "model_cache_stats"); 34 | } 35 | 36 | @Override 37 | public Collection getFabricDependencies() { 38 | return Lists.newArrayList(ResourceReloadListenerKeys.MODELS); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/client/resource/ModelCaches.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.client.resource; 2 | 3 | import it.unimi.dsi.fastutil.ints.IntArrays; 4 | import it.unimi.dsi.fastutil.objects.ObjectArrays; 5 | import me.jellysquid.mods.hydrogen.common.HydrogenMod; 6 | import me.jellysquid.mods.hydrogen.common.dedup.DeduplicationCache; 7 | 8 | public class ModelCaches { 9 | public static final DeduplicationCache QUADS = new DeduplicationCache<>(IntArrays.HASH_STRATEGY); 10 | public static final DeduplicationCache PROPERTIES = new DeduplicationCache<>(); 11 | 12 | public static void printDebug() { 13 | HydrogenMod.LOGGER.info("[[[ Model de-duplication statistics ]]]"); 14 | HydrogenMod.LOGGER.info("Baked quad cache: {}", QUADS); 15 | HydrogenMod.LOGGER.info("Properties cache: {}", PROPERTIES); 16 | } 17 | 18 | public static void cleanCaches() { 19 | QUADS.clearCache(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/HydrogenMod.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | 6 | public class HydrogenMod { 7 | public static final Logger LOGGER = LogManager.getLogger("Hydrogen"); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/HydrogenPreLaunch.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common; 2 | 3 | import me.jellysquid.mods.hydrogen.common.jvm.ClassConstructors; 4 | import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint; 5 | 6 | public class HydrogenPreLaunch implements PreLaunchEntrypoint { 7 | @Override 8 | public void onPreLaunch() { 9 | ClassConstructors.init(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/cache/StatePropertyTableCache.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.cache; 2 | 3 | import me.jellysquid.mods.hydrogen.common.collections.FastImmutableTableCache; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.fluid.Fluid; 7 | import net.minecraft.fluid.FluidState; 8 | import net.minecraft.state.property.Property; 9 | 10 | /** 11 | * Many of the column and row key arrays in block state tables will be duplicated, leading to an unnecessary waste of 12 | * memory. Since we have very limited options for trying to construct more optimized table types without throwing 13 | * maintainability or mod compatibility out the window, this class acts as a dirty way to find and de-duplicate arrays 14 | * after we construct our table types. 15 | * 16 | * While this global cache does not provide the ability to remove or clear entries from it, the reality is that it 17 | * shouldn't matter because block state tables are only initialized once and remain loaded for the entire lifetime of 18 | * the game. Even in the event of classloader pre-boot shenanigans, we still shouldn't leak memory as our cache will be 19 | * dropped along with the rest of the loaded classes when the class loader is reaped. 20 | */ 21 | public class StatePropertyTableCache { 22 | public static final FastImmutableTableCache, Comparable, BlockState> BLOCK_STATE_TABLE = 23 | new FastImmutableTableCache<>(); 24 | 25 | public static final FastImmutableTableCache, Comparable, FluidState> FLUID_STATE_TABLE = 26 | new FastImmutableTableCache<>(); 27 | 28 | @SuppressWarnings("unchecked") 29 | public static FastImmutableTableCache, Comparable, S> getTableCache(O owner) { 30 | if (owner instanceof Block) { 31 | return (FastImmutableTableCache, Comparable, S>) BLOCK_STATE_TABLE; 32 | } else if (owner instanceof Fluid) { 33 | return (FastImmutableTableCache, Comparable, S>) FLUID_STATE_TABLE; 34 | } else { 35 | throw new IllegalArgumentException(""); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/collections/CollectionHelper.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.collections; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.stream.Collector; 6 | import java.util.stream.Collectors; 7 | 8 | public class CollectionHelper { 9 | public static List fixed(List src) { 10 | return new FixedArrayList<>(src); 11 | } 12 | 13 | public static Collector> toSizedList(int size) { 14 | return Collectors.toCollection(() -> new ArrayList<>(size)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/collections/FastImmutableTable.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.collections; 2 | 3 | import com.google.common.collect.Table; 4 | import it.unimi.dsi.fastutil.Hash; 5 | import it.unimi.dsi.fastutil.HashCommon; 6 | import org.apache.commons.lang3.ArrayUtils; 7 | 8 | import java.util.Collection; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | import static it.unimi.dsi.fastutil.HashCommon.arraySize; 13 | 14 | public class FastImmutableTable implements Table { 15 | private R[] rowKeys; 16 | private int[] rowIndices; 17 | private final int rowMask; 18 | private final int rowCount; 19 | 20 | private C[] colKeys; 21 | private int[] colIndices; 22 | private final int colMask; 23 | private final int colCount; 24 | 25 | private V[] values; 26 | private final int size; 27 | 28 | @SuppressWarnings("unchecked") 29 | public FastImmutableTable(Table table, FastImmutableTableCache cache) { 30 | if (cache == null) { 31 | throw new IllegalArgumentException("Cache must not be null"); 32 | } 33 | 34 | float loadFactor = Hash.DEFAULT_LOAD_FACTOR; 35 | 36 | Set rowKeySet = table.rowKeySet(); 37 | Set colKeySet = table.columnKeySet(); 38 | 39 | this.rowCount = rowKeySet.size(); 40 | this.colCount = colKeySet.size(); 41 | 42 | int rowN = arraySize(this.rowCount, loadFactor); 43 | int colN = arraySize(this.colCount, loadFactor); 44 | 45 | this.rowMask = rowN - 1; 46 | this.rowKeys = (R[]) new Object[rowN]; 47 | this.rowIndices = new int[rowN]; 48 | 49 | this.colMask = colN - 1; 50 | this.colKeys = (C[]) new Object[colN]; 51 | this.colIndices = new int[colN]; 52 | 53 | this.createIndex(this.colKeys, this.colIndices, this.colMask, colKeySet); 54 | this.createIndex(this.rowKeys, this.rowIndices, this.rowMask, rowKeySet); 55 | 56 | this.values = (V[]) new Object[this.rowCount * this.colCount]; 57 | 58 | for (Cell cell : table.cellSet()) { 59 | int colIdx = this.getIndex(this.colKeys, this.colIndices, this.colMask, cell.getColumnKey()); 60 | int rowIdx = this.getIndex(this.rowKeys, this.rowIndices, this.rowMask, cell.getRowKey()); 61 | 62 | if (colIdx < 0 || rowIdx < 0) { 63 | throw new IllegalStateException("Missing index for " + cell); 64 | } 65 | 66 | this.values[this.colCount * rowIdx + colIdx] = cell.getValue(); 67 | } 68 | 69 | this.size = table.size(); 70 | 71 | this.rowKeys = cache.dedupRows(this.rowKeys); 72 | this.rowIndices = cache.dedupIndices(this.rowIndices); 73 | 74 | this.colIndices = cache.dedupIndices(this.colIndices); 75 | this.colKeys = cache.dedupColumns(this.colKeys); 76 | 77 | this.values = cache.dedupValues(this.values); 78 | } 79 | 80 | private void createIndex(T[] keys, int[] indices, int mask, Collection iterable) { 81 | int index = 0; 82 | 83 | for (T obj : iterable) { 84 | int i = this.find(keys, mask, obj); 85 | 86 | if (i < 0) { 87 | int pos = -i - 1; 88 | 89 | keys[pos] = obj; 90 | indices[pos] = index++; 91 | } 92 | } 93 | } 94 | 95 | private int getIndex(T[] keys, int[] indices, int mask, T key) { 96 | int pos = this.find(keys, mask, key); 97 | 98 | if (pos < 0) { 99 | return -1; 100 | } 101 | 102 | return indices[pos]; 103 | } 104 | 105 | @Override 106 | public boolean contains(Object rowKey, Object columnKey) { 107 | return this.get(rowKey, columnKey) != null; 108 | } 109 | 110 | @Override 111 | public boolean containsRow(Object rowKey) { 112 | return this.find(this.rowKeys, this.rowMask, rowKey) >= 0; 113 | } 114 | 115 | @Override 116 | public boolean containsColumn(Object columnKey) { 117 | return this.find(this.colKeys, this.colMask, columnKey) >= 0; 118 | } 119 | 120 | @Override 121 | public boolean containsValue(Object value) { 122 | return ArrayUtils.contains(this.values, value); 123 | } 124 | 125 | @Override 126 | public V get(Object rowKey, Object columnKey) { 127 | final int row = this.getIndex(this.rowKeys, this.rowIndices, this.rowMask, rowKey); 128 | final int col = this.getIndex(this.colKeys, this.colIndices, this.colMask, columnKey); 129 | 130 | if (row < 0 || col < 0) { 131 | return null; 132 | } 133 | 134 | return this.values[this.colCount * row + col]; 135 | } 136 | 137 | @Override 138 | public boolean isEmpty() { 139 | return this.size() == 0; 140 | } 141 | 142 | @Override 143 | public int size() { 144 | return this.size; 145 | } 146 | 147 | @Override 148 | public void clear() { 149 | throw new UnsupportedOperationException(); 150 | } 151 | 152 | @Override 153 | public V put(R rowKey, C columnKey, V val) { 154 | throw new UnsupportedOperationException(); 155 | } 156 | 157 | private int find(T[] key, int mask, T value) { 158 | T curr; 159 | int pos; 160 | // The starting point. 161 | if ((curr = key[pos = HashCommon.mix(value.hashCode()) & mask]) == null) { 162 | return -(pos + 1); 163 | } 164 | if (value.equals(curr)) { 165 | return pos; 166 | } 167 | // There's always an unused entry. 168 | while (true) { 169 | if ((curr = key[pos = pos + 1 & mask]) == null) { 170 | return -(pos + 1); 171 | } 172 | if (value.equals(curr)) { 173 | return pos; 174 | } 175 | } 176 | } 177 | 178 | @Override 179 | public void putAll(Table table) { 180 | throw new UnsupportedOperationException(); 181 | } 182 | 183 | @Override 184 | public V remove(Object rowKey, Object columnKey) { 185 | throw new UnsupportedOperationException(); 186 | } 187 | 188 | @Override 189 | public Map row(R rowKey) { 190 | throw new UnsupportedOperationException(); 191 | } 192 | 193 | @Override 194 | public Map column(C columnKey) { 195 | throw new UnsupportedOperationException(); 196 | } 197 | 198 | @Override 199 | public Set> cellSet() { 200 | throw new UnsupportedOperationException(); 201 | } 202 | 203 | @Override 204 | public Set rowKeySet() { 205 | throw new UnsupportedOperationException(); 206 | } 207 | 208 | @Override 209 | public Set columnKeySet() { 210 | throw new UnsupportedOperationException(); 211 | } 212 | 213 | @Override 214 | public Collection values() { 215 | throw new UnsupportedOperationException(); 216 | } 217 | 218 | @Override 219 | public Map> rowMap() { 220 | throw new UnsupportedOperationException(); 221 | } 222 | 223 | @Override 224 | public Map> columnMap() { 225 | throw new UnsupportedOperationException(); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/collections/FastImmutableTableCache.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.collections; 2 | 3 | import it.unimi.dsi.fastutil.Hash; 4 | import it.unimi.dsi.fastutil.ints.IntArrays; 5 | import it.unimi.dsi.fastutil.objects.ObjectArrays; 6 | import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; 7 | 8 | /** 9 | * @param The type used by the 10 | * @param 11 | * @param 12 | */ 13 | public class FastImmutableTableCache { 14 | private final ObjectOpenCustomHashSet rows; 15 | private final ObjectOpenCustomHashSet columns; 16 | private final ObjectOpenCustomHashSet values; 17 | 18 | private final ObjectOpenCustomHashSet indices; 19 | 20 | @SuppressWarnings("unchecked") 21 | public FastImmutableTableCache() { 22 | this.rows = new ObjectOpenCustomHashSet<>((Hash.Strategy) ObjectArrays.HASH_STRATEGY); 23 | this.columns = new ObjectOpenCustomHashSet<>((Hash.Strategy) ObjectArrays.HASH_STRATEGY); 24 | this.values = new ObjectOpenCustomHashSet<>((Hash.Strategy) ObjectArrays.HASH_STRATEGY); 25 | 26 | this.indices = new ObjectOpenCustomHashSet<>(IntArrays.HASH_STRATEGY); 27 | } 28 | 29 | public synchronized V[] dedupValues(V[] values) { 30 | return this.values.addOrGet(values); 31 | } 32 | 33 | public synchronized R[] dedupRows(R[] rows) { 34 | return this.rows.addOrGet(rows); 35 | } 36 | 37 | public synchronized C[] dedupColumns(C[] columns) { 38 | return this.columns.addOrGet(columns); 39 | } 40 | 41 | public synchronized int[] dedupIndices(int[] ints) { 42 | return this.indices.addOrGet(ints); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/collections/FixedArrayList.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.collections; 2 | 3 | import com.google.common.collect.Iterators; 4 | import org.apache.commons.lang3.ArrayUtils; 5 | 6 | import java.util.*; 7 | 8 | public class FixedArrayList implements List { 9 | private final T[] array; 10 | 11 | @SuppressWarnings("unchecked") 12 | public FixedArrayList(List list) { 13 | this(list.toArray((T[]) new Object[0])); 14 | } 15 | 16 | public FixedArrayList(T[] array) { 17 | this.array = array; 18 | } 19 | 20 | @Override 21 | public int size() { 22 | return this.array.length; 23 | } 24 | 25 | @Override 26 | public boolean isEmpty() { 27 | return this.array.length == 0; 28 | } 29 | 30 | @Override 31 | public boolean contains(Object o) { 32 | return ArrayUtils.contains(this.array, o); 33 | } 34 | 35 | @Override 36 | public Iterator iterator() { 37 | return Iterators.forArray(this.array); 38 | } 39 | 40 | @Override 41 | public Object[] toArray() { 42 | return this.array.clone(); 43 | } 44 | 45 | @SuppressWarnings("unchecked") 46 | @Override 47 | public T1[] toArray(T1[] dst) { 48 | T[] src = this.array; 49 | 50 | if (dst.length < src.length) { 51 | return (T1[]) Arrays.copyOf(src, src.length, dst.getClass()); 52 | } 53 | 54 | System.arraycopy(src, 0, dst, 0, src.length); 55 | 56 | if (dst.length > src.length) { 57 | dst[src.length] = null; 58 | } 59 | 60 | return dst; 61 | } 62 | 63 | @Override 64 | public boolean add(T t) { 65 | throw new UnsupportedOperationException(); 66 | } 67 | 68 | @Override 69 | public boolean remove(Object o) { 70 | throw new UnsupportedOperationException(); 71 | } 72 | 73 | @Override 74 | public boolean containsAll(Collection c) { 75 | for (Object o : c) { 76 | if (!ArrayUtils.contains(this.array, o)) { 77 | return false; 78 | } 79 | } 80 | 81 | return true; 82 | } 83 | 84 | @Override 85 | public boolean addAll(Collection c) { 86 | throw new UnsupportedOperationException(); 87 | } 88 | 89 | @Override 90 | public boolean addAll(int index, Collection c) { 91 | throw new UnsupportedOperationException(); 92 | } 93 | 94 | @Override 95 | public boolean removeAll(Collection c) { 96 | throw new UnsupportedOperationException(); 97 | } 98 | 99 | @Override 100 | public boolean retainAll(Collection c) { 101 | throw new UnsupportedOperationException(); 102 | } 103 | 104 | @Override 105 | public void clear() { 106 | throw new UnsupportedOperationException(); 107 | } 108 | 109 | @Override 110 | public T get(int index) { 111 | return this.array[index]; 112 | } 113 | 114 | @Override 115 | public T set(int index, T element) { 116 | throw new UnsupportedOperationException(); 117 | } 118 | 119 | @Override 120 | public void add(int index, T element) { 121 | throw new UnsupportedOperationException(); 122 | } 123 | 124 | @Override 125 | public T remove(int index) { 126 | throw new UnsupportedOperationException(); 127 | } 128 | 129 | @Override 130 | public int indexOf(Object o) { 131 | return ArrayUtils.indexOf(this.array, o); 132 | } 133 | 134 | @Override 135 | public int lastIndexOf(Object o) { 136 | return ArrayUtils.lastIndexOf(this.array, o); 137 | } 138 | 139 | @Override 140 | public ListIterator listIterator() { 141 | throw new UnsupportedOperationException(); 142 | } 143 | 144 | @Override 145 | public ListIterator listIterator(int index) { 146 | throw new UnsupportedOperationException(); 147 | } 148 | 149 | @Override 150 | public List subList(int fromIndex, int toIndex) { 151 | throw new UnsupportedOperationException(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/collections/ImmutablePairArrayList.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.collections; 2 | 3 | import com.google.common.collect.AbstractIterator; 4 | import org.apache.commons.lang3.tuple.Pair; 5 | 6 | import java.util.Collection; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.ListIterator; 10 | 11 | public class ImmutablePairArrayList implements List> { 12 | private final V1[] v1; 13 | private final V2[] v2; 14 | private final int size; 15 | 16 | private final MutablePair pair = new MutablePair<>(); 17 | 18 | @SuppressWarnings("unchecked") 19 | public ImmutablePairArrayList(List> src) { 20 | this.size = src.size(); 21 | 22 | this.v1 = (V1[]) new Object[this.size]; 23 | this.v2 = (V2[]) new Object[this.size]; 24 | 25 | for (int i = 0; i < this.size; i++) { 26 | Pair pair = src.get(i); 27 | 28 | this.v1[i] = pair.getKey(); 29 | this.v2[i] = pair.getValue(); 30 | } 31 | } 32 | 33 | @Override 34 | public int size() { 35 | return this.size; 36 | } 37 | 38 | @Override 39 | public boolean isEmpty() { 40 | return this.size() == 0; 41 | } 42 | 43 | @Override 44 | public boolean contains(Object o) { 45 | if (!(o instanceof Pair)) { 46 | return false; 47 | } 48 | 49 | Pair pair = (Pair) o; 50 | 51 | for (int i = 0; i < this.size; i++) { 52 | if (this.v1[i] == pair.getLeft() && this.v2[i] == pair.getRight()) { 53 | return true; 54 | } 55 | } 56 | 57 | return false; 58 | } 59 | 60 | @Override 61 | public Iterator> iterator() { 62 | return new AbstractIterator>() { 63 | private final V1[] v1 = ImmutablePairArrayList.this.v1; 64 | private final V2[] v2 = ImmutablePairArrayList.this.v2; 65 | private final int limit = ImmutablePairArrayList.this.size; 66 | 67 | private final MutablePair tmp = new MutablePair<>(); 68 | 69 | private int index = 0; 70 | 71 | @Override 72 | protected Pair computeNext() { 73 | if (this.index >= this.limit) { 74 | return this.endOfData(); 75 | } 76 | 77 | this.tmp.left = this.v1[this.index]; 78 | this.tmp.right = this.v2[this.index]; 79 | 80 | this.index++; 81 | 82 | return this.tmp; 83 | } 84 | }; 85 | } 86 | 87 | @Override 88 | public Object[] toArray() { 89 | throw new UnsupportedOperationException(); 90 | } 91 | 92 | @Override 93 | public T[] toArray(T[] a) { 94 | throw new UnsupportedOperationException(); 95 | } 96 | 97 | @Override 98 | public boolean add(Pair v1V2Pair) { 99 | throw new UnsupportedOperationException(); 100 | } 101 | 102 | @Override 103 | public boolean remove(Object o) { 104 | throw new UnsupportedOperationException(); 105 | } 106 | 107 | @Override 108 | public boolean containsAll(Collection collection) { 109 | for (Object obj : collection) { 110 | if (!this.contains(obj)) { 111 | return false; 112 | } 113 | } 114 | 115 | return true; 116 | } 117 | 118 | @Override 119 | public boolean addAll(Collection> c) { 120 | throw new UnsupportedOperationException(); 121 | } 122 | 123 | @Override 124 | public boolean addAll(int index, Collection> c) { 125 | throw new UnsupportedOperationException(); 126 | } 127 | 128 | @Override 129 | public boolean removeAll(Collection c) { 130 | throw new UnsupportedOperationException(); 131 | } 132 | 133 | @Override 134 | public boolean retainAll(Collection c) { 135 | throw new UnsupportedOperationException(); 136 | } 137 | 138 | @Override 139 | public void clear() { 140 | throw new UnsupportedOperationException(); 141 | } 142 | 143 | @Override 144 | public Pair get(int index) { 145 | this.pair.left = this.v1[index]; 146 | this.pair.right = this.v2[index]; 147 | 148 | return this.pair; 149 | } 150 | 151 | @Override 152 | public Pair set(int index, Pair element) { 153 | throw new UnsupportedOperationException(); 154 | } 155 | 156 | @Override 157 | public void add(int index, Pair element) { 158 | throw new UnsupportedOperationException(); 159 | } 160 | 161 | @Override 162 | public Pair remove(int index) { 163 | throw new UnsupportedOperationException(); 164 | } 165 | 166 | @Override 167 | public int indexOf(Object o) { 168 | throw new UnsupportedOperationException(); 169 | } 170 | 171 | @Override 172 | public int lastIndexOf(Object o) { 173 | throw new UnsupportedOperationException(); 174 | } 175 | 176 | @Override 177 | public ListIterator> listIterator() { 178 | throw new UnsupportedOperationException(); 179 | } 180 | 181 | @Override 182 | public ListIterator> listIterator(int index) { 183 | throw new UnsupportedOperationException(); 184 | } 185 | 186 | @Override 187 | public List> subList(int fromIndex, int toIndex) { 188 | throw new UnsupportedOperationException(); 189 | } 190 | 191 | private static class MutablePair extends Pair { 192 | private V1 left; 193 | private V2 right; 194 | 195 | @Override 196 | public V1 getLeft() { 197 | return this.left; 198 | } 199 | 200 | @Override 201 | public V2 getRight() { 202 | return this.right; 203 | } 204 | 205 | @Override 206 | public V2 setValue(V2 value) { 207 | throw new UnsupportedOperationException(); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/dedup/DeduplicationCache.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.dedup; 2 | 3 | import it.unimi.dsi.fastutil.Hash; 4 | import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; 5 | 6 | import java.util.Objects; 7 | 8 | public class DeduplicationCache { 9 | private final ObjectOpenCustomHashSet pool; 10 | 11 | private int attemptedInsertions = 0; 12 | private int deduplicated = 0; 13 | 14 | public DeduplicationCache(Hash.Strategy strategy) { 15 | this.pool = new ObjectOpenCustomHashSet<>(strategy); 16 | } 17 | 18 | public DeduplicationCache() { 19 | this.pool = new ObjectOpenCustomHashSet<>(new Hash.Strategy() { 20 | @Override 21 | public int hashCode(T o) { 22 | return Objects.hashCode(o); 23 | } 24 | 25 | @Override 26 | public boolean equals(T a, T b) { 27 | return Objects.equals(a, b); 28 | } 29 | }); 30 | } 31 | 32 | public synchronized T deduplicate(T item) { 33 | this.attemptedInsertions++; 34 | 35 | T result = this.pool.addOrGet(item); 36 | 37 | if (result != item) { 38 | this.deduplicated++; 39 | } 40 | 41 | return result; 42 | } 43 | 44 | public synchronized void clearCache() { 45 | this.attemptedInsertions = 0; 46 | this.deduplicated = 0; 47 | 48 | this.pool.clear(); 49 | } 50 | 51 | @Override 52 | public synchronized String toString() { 53 | return String.format("DeduplicationCache ( %d/%d de-duplicated, %d pooled )", 54 | this.deduplicated, this.attemptedInsertions, this.pool.size()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/dedup/IdentifierCaches.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.dedup; 2 | 3 | import me.jellysquid.mods.hydrogen.common.HydrogenMod; 4 | 5 | public class IdentifierCaches { 6 | public static final DeduplicationCache NAMESPACES = new DeduplicationCache<>(); 7 | public static final DeduplicationCache PATH = new DeduplicationCache<>(); 8 | 9 | public static void printDebug() { 10 | HydrogenMod.LOGGER.info("[[[ Identifier de-duplication statistics ]]]"); 11 | HydrogenMod.LOGGER.info("Namespace cache: {}", NAMESPACES); 12 | HydrogenMod.LOGGER.info("Path cache: {}", PATH); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/jvm/ClassConstructors.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.jvm; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodType; 8 | import java.lang.reflect.Constructor; 9 | import java.util.Map; 10 | 11 | @SuppressWarnings("unchecked") 12 | public class ClassConstructors { 13 | private static MethodHandle FAST_IMMUTABLE_REFERENCE_HASH_MAP_CONSTRUCTOR; 14 | 15 | public static void init() { 16 | initGuavaExtensions(); 17 | } 18 | 19 | private static void initGuavaExtensions() { 20 | // define classes in their reverse link order to prevent duplicate definition issues 21 | ClassDefineTool.defineClass(ImmutableMap.class, "com.google.common.collect.HydrogenImmutableMapEntry"); 22 | ClassDefineTool.defineClass(ImmutableMap.class, "com.google.common.collect.HydrogenEntrySetIterator"); 23 | ClassDefineTool.defineClass(ImmutableMap.class, "com.google.common.collect.HydrogenEntrySet"); 24 | 25 | Class immutableRefHashMapClass = ClassDefineTool.defineClass(ImmutableMap.class, "com.google.common.collect.HydrogenImmutableReferenceHashMap"); 26 | 27 | try { 28 | FAST_IMMUTABLE_REFERENCE_HASH_MAP_CONSTRUCTOR = MethodHandles.lookup() 29 | .findConstructor(immutableRefHashMapClass, MethodType.methodType(Void.TYPE, Map.class)) 30 | // compiler can only generate a desc returning ImmutableMap below 31 | .asType(MethodType.methodType(ImmutableMap.class, Map.class)); 32 | } catch (ReflectiveOperationException e) { 33 | throw new RuntimeException("Failed to find constructor", e); 34 | } 35 | } 36 | 37 | public static ImmutableMap createFastImmutableMap(Map orig) { 38 | try { 39 | return (ImmutableMap) FAST_IMMUTABLE_REFERENCE_HASH_MAP_CONSTRUCTOR.invokeExact((Map) orig); 40 | } catch (Throwable e) { 41 | throw new RuntimeException("Could not instantiate collection", e); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/jvm/ClassDefineTool.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.jvm; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | 7 | import java.io.IOException; 8 | import java.lang.invoke.MethodHandles; 9 | import java.net.URL; 10 | 11 | public class ClassDefineTool { 12 | private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 13 | 14 | private static final Logger LOGGER = LogManager.getLogger("Hydrogen"); 15 | 16 | public static Class defineClass(Class context, String name) { 17 | String path = "/" + name.replace('.', '/') + ".class"; 18 | URL url = ClassDefineTool.class.getResource(path); 19 | 20 | if (url == null) { 21 | throw new RuntimeException("Couldn't find resource: " + path); 22 | } 23 | 24 | LOGGER.info("Injecting class '{}' (url: {})", name, url); 25 | 26 | byte[] code; 27 | 28 | try { 29 | code = IOUtils.toByteArray(url); 30 | } catch (IOException e) { 31 | throw new RuntimeException("Could not read class bytes from resources: " + path, e); 32 | } 33 | 34 | try { 35 | // The context class need to be in a module that exports and opens to ClassDefineTool 36 | // Example: guava's automatic module exports and opens to everything 37 | MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(context, LOOKUP); 38 | return privateLookup.defineClass(code); 39 | } catch (Throwable throwable) { 40 | throw new RuntimeException("Failed to define class", throwable); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/state/StatePropertyPredicateHelper.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.state; 2 | 3 | import me.jellysquid.mods.hydrogen.common.state.all.AllMatchOneBoolean; 4 | import me.jellysquid.mods.hydrogen.common.state.all.AllMatchOneObject; 5 | import me.jellysquid.mods.hydrogen.common.state.any.AllMatchAnyObject; 6 | import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchAny; 7 | import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchOne; 8 | import me.jellysquid.mods.hydrogen.common.util.AllPredicate; 9 | import me.jellysquid.mods.hydrogen.common.util.AnyPredicate; 10 | import net.minecraft.block.BlockState; 11 | 12 | import java.util.List; 13 | import java.util.function.Predicate; 14 | 15 | public class StatePropertyPredicateHelper { 16 | @SuppressWarnings("unchecked") 17 | public static Predicate allMatch(List> predicates) { 18 | if (SingleMatchOne.areOfType(predicates)) { 19 | if (SingleMatchOne.valuesMatchType(predicates, Boolean.class)) { 20 | return new AllMatchOneBoolean(predicates); 21 | } 22 | 23 | return new AllMatchOneObject(predicates); 24 | } else if (SingleMatchAny.areOfType(predicates)) { 25 | return new AllMatchAnyObject(predicates); 26 | } 27 | 28 | return new AllPredicate<>(predicates.toArray(new Predicate[0])); 29 | } 30 | 31 | @SuppressWarnings("unchecked") 32 | public static Predicate anyMatch(List> predicates) { 33 | return new AnyPredicate<>(predicates.toArray(new Predicate[0])); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneBoolean.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.state.all; 2 | 3 | import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchOne; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.state.property.Property; 6 | 7 | import java.util.List; 8 | import java.util.function.Predicate; 9 | 10 | public class AllMatchOneBoolean implements Predicate { 11 | private final Property[] properties; 12 | private final boolean[] values; 13 | 14 | public AllMatchOneBoolean(List> list) { 15 | int size = list.size(); 16 | 17 | this.properties = new Property[size]; 18 | this.values = new boolean[size]; 19 | 20 | for (int i = 0; i < size; i++) { 21 | SingleMatchOne predicate = (SingleMatchOne) list.get(i); 22 | 23 | this.properties[i] = predicate.property; 24 | this.values[i] = (boolean) predicate.value; 25 | } 26 | } 27 | 28 | public static boolean canReplace(List> list) { 29 | return list.stream() 30 | .allMatch(p -> { 31 | return p instanceof SingleMatchOne && ((SingleMatchOne) p).value instanceof Boolean; 32 | }); 33 | } 34 | 35 | @Override 36 | public boolean test(BlockState blockState) { 37 | for (int i = 0; i < this.properties.length; i++) { 38 | Boolean value = (Boolean) blockState.get(this.properties[i]); 39 | 40 | if (value != this.values[i]) { 41 | return false; 42 | } 43 | } 44 | 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneObject.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.state.all; 2 | 3 | import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchOne; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.state.property.Property; 6 | 7 | import java.util.List; 8 | import java.util.function.Predicate; 9 | 10 | public class AllMatchOneObject implements Predicate { 11 | private final Property[] properties; 12 | private final Object[] values; 13 | 14 | public AllMatchOneObject(List> list) { 15 | int size = list.size(); 16 | 17 | this.properties = new Property[size]; 18 | this.values = new Object[size]; 19 | 20 | for (int i = 0; i < size; i++) { 21 | SingleMatchOne predicate = (SingleMatchOne) list.get(i); 22 | 23 | this.properties[i] = predicate.property; 24 | this.values[i] = predicate.value; 25 | } 26 | } 27 | 28 | @Override 29 | public boolean test(BlockState blockState) { 30 | for (int i = 0; i < this.properties.length; i++) { 31 | if (blockState.get(this.properties[i]) != this.values[i]) { 32 | return false; 33 | } 34 | } 35 | 36 | return true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/state/any/AllMatchAnyObject.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.state.any; 2 | 3 | import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchAny; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.state.property.Property; 6 | import org.apache.commons.lang3.ArrayUtils; 7 | 8 | import java.util.List; 9 | import java.util.function.Predicate; 10 | 11 | public class AllMatchAnyObject implements Predicate { 12 | private final Property[] properties; 13 | private final Object[][] values; 14 | 15 | public AllMatchAnyObject(List> list) { 16 | int size = list.size(); 17 | 18 | this.properties = new Property[size]; 19 | this.values = new Object[size][]; 20 | 21 | for (int i = 0; i < size; i++) { 22 | SingleMatchAny predicate = (SingleMatchAny) list.get(i); 23 | 24 | this.properties[i] = predicate.property; 25 | this.values[i] = predicate.values; 26 | } 27 | } 28 | 29 | @Override 30 | public boolean test(BlockState blockState) { 31 | for (int i = 0; i < this.properties.length; i++) { 32 | if (!ArrayUtils.contains(this.values[i], blockState.get(this.properties[i]))) { 33 | return false; 34 | } 35 | } 36 | 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchAny.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.state.single; 2 | 3 | import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.state.property.Property; 6 | import org.apache.commons.lang3.ArrayUtils; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.Objects; 11 | import java.util.function.Predicate; 12 | 13 | public class SingleMatchAny implements Predicate { 14 | public static final ObjectOpenHashSet PREDICATES = new ObjectOpenHashSet<>(); 15 | 16 | public final Property property; 17 | public final Object[] values; 18 | 19 | private SingleMatchAny(Property property, List values) { 20 | this.property = property; 21 | this.values = values.toArray(); 22 | } 23 | 24 | public static SingleMatchAny create(Property property, List values) { 25 | return PREDICATES.addOrGet(new SingleMatchAny(property, values)); 26 | } 27 | 28 | public static boolean areOfType(List> predicates) { 29 | return predicates.stream() 30 | .allMatch(p -> { 31 | return p instanceof SingleMatchAny; 32 | }); 33 | } 34 | 35 | public static boolean valuesMatchType(List> predicates, Class type) { 36 | return predicates.stream() 37 | .allMatch(p -> { 38 | return p instanceof SingleMatchAny && 39 | Arrays.stream(((SingleMatchAny) p).values).allMatch(t -> type.isInstance(p)); 40 | }); 41 | } 42 | 43 | @Override 44 | public boolean test(BlockState blockState) { 45 | return ArrayUtils.contains(this.values, blockState.get(this.property)); 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (this == o) return true; 51 | if (o == null || getClass() != o.getClass()) return false; 52 | SingleMatchAny that = (SingleMatchAny) o; 53 | return Objects.equals(property, that.property) && 54 | Arrays.equals(values, that.values); 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | int result = Objects.hash(property); 60 | result = 31 * result + Arrays.hashCode(values); 61 | return result; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchOne.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.state.single; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.state.property.Property; 5 | 6 | import java.util.List; 7 | import java.util.function.Predicate; 8 | 9 | public class SingleMatchOne implements Predicate { 10 | public final Property property; 11 | public final Object value; 12 | 13 | public SingleMatchOne(Property property, Object value) { 14 | this.property = property; 15 | this.value = value; 16 | } 17 | 18 | public static boolean areOfType(List> predicates) { 19 | return predicates.stream() 20 | .allMatch(p -> { 21 | return p instanceof SingleMatchOne; 22 | }); 23 | } 24 | 25 | public static boolean valuesMatchType(List> predicates, Class type) { 26 | return predicates.stream() 27 | .allMatch(p -> { 28 | return p instanceof SingleMatchOne && type.isInstance(((SingleMatchOne) p).value); 29 | }); 30 | } 31 | 32 | @Override 33 | public boolean test(BlockState blockState) { 34 | return blockState.get(this.property) == this.value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/util/AllPredicate.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.util; 2 | 3 | import java.util.function.Predicate; 4 | 5 | public class AllPredicate implements Predicate { 6 | private final Predicate[] predicates; 7 | 8 | public AllPredicate(Predicate[] predicates) { 9 | this.predicates = predicates; 10 | } 11 | 12 | @Override 13 | public boolean test(T t) { 14 | for (Predicate predicate : this.predicates) { 15 | if (!predicate.test(t)) { 16 | return false; 17 | } 18 | } 19 | 20 | return true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/common/util/AnyPredicate.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.common.util; 2 | 3 | import java.util.function.Predicate; 4 | 5 | public class AnyPredicate implements Predicate { 6 | private final Predicate[] predicates; 7 | 8 | public AnyPredicate(Predicate[] predicates) { 9 | this.predicates = predicates; 10 | } 11 | 12 | @Override 13 | public boolean test(T t) { 14 | for (Predicate predicate : this.predicates) { 15 | if (predicate.test(t)) { 16 | return true; 17 | } 18 | } 19 | 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/chunk/MixinChunkSerializer.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.chunk; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.fluid.Fluid; 5 | import net.minecraft.nbt.NbtCompound; 6 | import net.minecraft.server.world.ServerWorld; 7 | import net.minecraft.structure.StructureManager; 8 | import net.minecraft.util.math.ChunkPos; 9 | import net.minecraft.world.ChunkSerializer; 10 | import net.minecraft.world.TickScheduler; 11 | import net.minecraft.world.World; 12 | import net.minecraft.world.biome.source.BiomeArray; 13 | import net.minecraft.world.chunk.ChunkSection; 14 | import net.minecraft.world.chunk.ProtoChunk; 15 | import net.minecraft.world.chunk.UpgradeData; 16 | import net.minecraft.world.chunk.WorldChunk; 17 | import net.minecraft.world.poi.PointOfInterestStorage; 18 | import org.jetbrains.annotations.Nullable; 19 | import org.spongepowered.asm.mixin.Mixin; 20 | import org.spongepowered.asm.mixin.Shadow; 21 | import org.spongepowered.asm.mixin.injection.At; 22 | import org.spongepowered.asm.mixin.injection.Inject; 23 | import org.spongepowered.asm.mixin.injection.Redirect; 24 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 25 | 26 | import java.util.function.Consumer; 27 | 28 | @Mixin(ChunkSerializer.class) 29 | public abstract class MixinChunkSerializer { 30 | private static final ThreadLocal CAPTURED_TAGS = new ThreadLocal<>(); 31 | 32 | @Shadow 33 | private static void loadEntities(ServerWorld world, NbtCompound nbt, WorldChunk chunk) { 34 | throw new UnsupportedOperationException(); 35 | } 36 | 37 | @Inject(method = "deserialize", at = @At("HEAD")) 38 | private static void captureTag(ServerWorld world, StructureManager structureManager, PointOfInterestStorage poiStorage, ChunkPos pos, NbtCompound tag, CallbackInfoReturnable cir) { 39 | // We can't access the method parameters in the later redirect, so capture them for this thread 40 | CAPTURED_TAGS.set(tag); 41 | } 42 | 43 | @Redirect(method = "deserialize", at = @At(value = "NEW", target = "net/minecraft/world/chunk/WorldChunk")) 44 | private static WorldChunk create(World world, ChunkPos pos, BiomeArray biomes, UpgradeData upgradeData, 45 | TickScheduler blockTickScheduler, TickScheduler fluidTickScheduler, 46 | long inhabitedTime, @Nullable ChunkSection[] sections, 47 | @Nullable Consumer loadToWorldConsumer) { 48 | NbtCompound rootTag = CAPTURED_TAGS.get(); 49 | 50 | if (rootTag == null) { 51 | throw new IllegalStateException("No captured tag was found"); 52 | } 53 | 54 | NbtCompound level = rootTag.getCompound("Level"); 55 | 56 | // The (misleadingly named) writeEntities function below only cares about these two tags 57 | // However, the lambda can end up staying loaded with the chunk if it isn't within ticking radius of a player yet 58 | // In order to prevent huge NBT blobs from remaining loaded in memory all the time, we can strip all the other 59 | // data to save a fair bit of memory. 60 | NbtCompound strippedTag = new NbtCompound(); 61 | strippedTag.put("Entities", level.getList("Entities", 10)); 62 | strippedTag.put("TileEntities", level.getList("TileEntities", 10)); 63 | 64 | return new WorldChunk(world, pos, biomes, upgradeData, blockTickScheduler, fluidTickScheduler, inhabitedTime, sections, (chunk) -> { 65 | loadEntities((ServerWorld) world, strippedTag, chunk); 66 | }); 67 | } 68 | 69 | @Inject(method = "deserialize", at = @At("RETURN")) 70 | private static void releaseTag(ServerWorld world, StructureManager structureManager, PointOfInterestStorage poiStorage, ChunkPos pos, NbtCompound tag, CallbackInfoReturnable cir) { 71 | // Avoid leaking tags in memory 72 | CAPTURED_TAGS.remove(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/chunk/MixinWorldChunk.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.chunk; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.fluid.Fluid; 5 | import net.minecraft.util.math.ChunkPos; 6 | import net.minecraft.world.TickScheduler; 7 | import net.minecraft.world.World; 8 | import net.minecraft.world.biome.source.BiomeArray; 9 | import net.minecraft.world.chunk.ChunkSection; 10 | import net.minecraft.world.chunk.UpgradeData; 11 | import net.minecraft.world.chunk.WorldChunk; 12 | import org.spongepowered.asm.mixin.Final; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | 19 | import java.util.function.Consumer; 20 | 21 | @Mixin(WorldChunk.class) 22 | public class MixinWorldChunk { 23 | @Shadow 24 | @Final 25 | private ChunkSection[] sections; 26 | 27 | @Inject(method = "(Lnet/minecraft/world/World;Lnet/minecraft/util/math/ChunkPos;" + 28 | "Lnet/minecraft/world/biome/source/BiomeArray;Lnet/minecraft/world/chunk/UpgradeData;" + 29 | "Lnet/minecraft/world/TickScheduler;Lnet/minecraft/world/TickScheduler;J[" + 30 | "Lnet/minecraft/world/chunk/ChunkSection;Ljava/util/function/Consumer;)V",at = @At("RETURN")) 31 | private void reinit(World world, ChunkPos pos, BiomeArray biomes, UpgradeData upgradeData, 32 | TickScheduler blockTickScheduler, TickScheduler fluidTickScheduler, 33 | long inhabitedTime, ChunkSection[] sections, Consumer loadToWorldConsumer, 34 | CallbackInfo ci) { 35 | // Upgrading a ProtoChunk to a WorldChunk might result in empty sections being copied over 36 | // These simply waste memory, and the WorldChunk will return air blocks for any absent section without issue. 37 | for (int i = 0; i < this.sections.length; i++) { 38 | if (ChunkSection.isEmpty(this.sections[i])) { 39 | this.sections[i] = null; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/MixinBakedQuad.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.client.model; 2 | 3 | import me.jellysquid.mods.hydrogen.client.resource.ModelCaches; 4 | import net.minecraft.client.render.model.BakedQuad; 5 | import net.minecraft.client.texture.Sprite; 6 | import net.minecraft.util.math.Direction; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Mutable; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | @Mixin(BakedQuad.class) 16 | public class MixinBakedQuad { 17 | @Mutable 18 | @Shadow 19 | @Final 20 | protected int[] vertexData; 21 | 22 | @Inject(method = "", at = @At("RETURN")) 23 | private void reinit(int[] vertexData, int colorIndex, Direction face, Sprite sprite, boolean shade, CallbackInfo ci) { 24 | this.vertexData = ModelCaches.QUADS.deduplicate(this.vertexData); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/MixinBasicBakedModel.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.client.model; 2 | 3 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 4 | import me.jellysquid.mods.hydrogen.common.collections.CollectionHelper; 5 | import net.minecraft.client.render.model.BakedQuad; 6 | import net.minecraft.client.render.model.BasicBakedModel; 7 | import net.minecraft.client.render.model.json.ModelOverrideList; 8 | import net.minecraft.client.render.model.json.ModelTransformation; 9 | import net.minecraft.client.texture.Sprite; 10 | import net.minecraft.util.math.Direction; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Mutable; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | @Mixin(BasicBakedModel.class) 23 | public class MixinBasicBakedModel { 24 | @Mutable 25 | @Shadow 26 | @Final 27 | protected Map> faceQuads; 28 | 29 | @Mutable 30 | @Shadow 31 | @Final 32 | protected List quads; 33 | 34 | @Inject(method = "", at = @At("RETURN")) 35 | private void reinit(List quads, Map> faceQuads, boolean usesAo, boolean isSideLit, boolean hasDepth, Sprite sprite, ModelTransformation modelTransformation, ModelOverrideList modelOverrideList, CallbackInfo ci) { 36 | this.quads = CollectionHelper.fixed(this.quads); 37 | 38 | for (Map.Entry> entry : this.faceQuads.entrySet()) { 39 | entry.setValue(CollectionHelper.fixed(entry.getValue())); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/MixinModelIdentifier.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.client.model; 2 | 3 | import me.jellysquid.mods.hydrogen.client.model.HydrogenModelIdentifier; 4 | import me.jellysquid.mods.hydrogen.client.resource.ModelCaches; 5 | import net.minecraft.client.util.ModelIdentifier; 6 | import org.spongepowered.asm.mixin.*; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | import java.util.Arrays; 12 | 13 | @Mixin(ModelIdentifier.class) 14 | public class MixinModelIdentifier implements HydrogenModelIdentifier { 15 | @Mutable 16 | @Shadow 17 | @Final 18 | private String variant; 19 | 20 | private String[] properties; 21 | 22 | @Inject(method = "([Ljava/lang/String;)V", at = @At("RETURN")) 23 | private void reinit(String[] strings, CallbackInfo ci) { 24 | this.properties = Arrays.stream(this.variant.split(",")) 25 | .map(ModelCaches.PROPERTIES::deduplicate) 26 | .toArray(String[]::new); 27 | 28 | // Poison any code path using this variable. 29 | this.variant = null; 30 | } 31 | 32 | /** 33 | * @author JellySquid 34 | * @reason Use properties array 35 | */ 36 | @Overwrite 37 | public String getVariant() { 38 | return String.join(",", this.properties); 39 | } 40 | 41 | /** 42 | * @author JellySquid 43 | * @reason Use properties array 44 | */ 45 | @Overwrite 46 | public boolean equals(Object o) { 47 | if (this == o) { 48 | return true; 49 | } else if (o instanceof ModelIdentifier && super.equals(o)) { 50 | return Arrays.equals(this.properties, ((HydrogenModelIdentifier) o).getProperties()); 51 | } else { 52 | return false; 53 | } 54 | } 55 | 56 | /** 57 | * @author JellySquid 58 | * @reason Use properties array 59 | */ 60 | @Overwrite 61 | public int hashCode() { 62 | return 31 * super.hashCode() + Arrays.hashCode(this.properties); 63 | } 64 | 65 | /** 66 | * @author JellySquid 67 | * @reason Use properties array 68 | */ 69 | @Overwrite 70 | public String toString() { 71 | return super.toString() + "#" + this.getVariant(); 72 | } 73 | 74 | @Override 75 | public String[] getProperties() { 76 | return this.properties; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/MixinModelLoader.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.client.model; 2 | 3 | import com.mojang.datafixers.util.Pair; 4 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 5 | import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; 6 | import net.minecraft.client.color.block.BlockColors; 7 | import net.minecraft.client.render.model.BakedModel; 8 | import net.minecraft.client.render.model.ModelLoader; 9 | import net.minecraft.client.render.model.UnbakedModel; 10 | import net.minecraft.client.texture.SpriteAtlasTexture; 11 | import net.minecraft.resource.ResourceManager; 12 | import net.minecraft.util.Identifier; 13 | import net.minecraft.util.math.AffineTransformation; 14 | import net.minecraft.util.profiler.Profiler; 15 | import org.apache.commons.lang3.tuple.Triple; 16 | import org.spongepowered.asm.mixin.Final; 17 | import org.spongepowered.asm.mixin.Mixin; 18 | import org.spongepowered.asm.mixin.Mutable; 19 | import org.spongepowered.asm.mixin.Shadow; 20 | import org.spongepowered.asm.mixin.injection.At; 21 | import org.spongepowered.asm.mixin.injection.Inject; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 23 | 24 | import java.util.Map; 25 | import java.util.Set; 26 | 27 | @Mixin(ModelLoader.class) 28 | public class MixinModelLoader { 29 | @Mutable 30 | @Shadow 31 | @Final 32 | private Map unbakedModels; 33 | 34 | @Mutable 35 | @Shadow 36 | @Final 37 | private Map, BakedModel> bakedModelCache; 38 | 39 | @Mutable 40 | @Shadow 41 | @Final 42 | private Map bakedModels; 43 | 44 | @Mutable 45 | @Shadow 46 | @Final 47 | private Map modelsToBake; 48 | 49 | @Mutable 50 | @Shadow 51 | @Final 52 | private Set modelsToLoad; 53 | 54 | @Mutable 55 | @Shadow 56 | @Final 57 | private Map> spriteAtlasData; 58 | 59 | @Inject(method = "", at = @At("RETURN")) 60 | private void reinit(ResourceManager resourceManager, BlockColors blockColors, Profiler profiler, int i, CallbackInfo ci) { 61 | this.unbakedModels = new Object2ObjectOpenHashMap<>(this.unbakedModels); 62 | this.bakedModelCache = new Object2ObjectOpenHashMap<>(this.bakedModelCache); 63 | this.bakedModels = new Object2ObjectOpenHashMap<>(this.bakedModels); 64 | this.modelsToBake = new Object2ObjectOpenHashMap<>(this.modelsToBake); 65 | this.modelsToLoad = new ObjectOpenHashSet<>(this.modelsToLoad); 66 | this.spriteAtlasData = new Object2ObjectOpenHashMap<>(this.spriteAtlasData); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/MixinMultipartBakedModel.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.client.model; 2 | 3 | import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; 4 | import me.jellysquid.mods.hydrogen.common.collections.ImmutablePairArrayList; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.client.render.model.BakedModel; 7 | import net.minecraft.client.render.model.MultipartBakedModel; 8 | import org.apache.commons.lang3.tuple.Pair; 9 | import org.spongepowered.asm.mixin.Final; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Mutable; 12 | import org.spongepowered.asm.mixin.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 java.util.BitSet; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.function.Predicate; 21 | 22 | @Mixin(MultipartBakedModel.class) 23 | public class MixinMultipartBakedModel { 24 | @Mutable 25 | @Shadow 26 | @Final 27 | private List, BakedModel>> components; 28 | 29 | @Mutable 30 | @Shadow 31 | @Final 32 | private Map stateCache; 33 | 34 | @Inject(method = "", at = @At("RETURN")) 35 | private void reinit(List, BakedModel>> components, CallbackInfo ci) { 36 | this.stateCache = new Reference2ObjectOpenHashMap<>(this.stateCache); 37 | this.components = new ImmutablePairArrayList<>(this.components); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/MixinWeightedBakedModel.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.client.model; 2 | 3 | import me.jellysquid.mods.hydrogen.common.collections.CollectionHelper; 4 | import net.minecraft.client.render.model.BakedModel; 5 | import net.minecraft.client.render.model.WeightedBakedModel; 6 | import net.minecraft.util.collection.Weighted; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Mutable; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | @Mixin(WeightedBakedModel.class) 19 | public class MixinWeightedBakedModel { 20 | @Mutable 21 | @Shadow 22 | @Final 23 | private List> models; 24 | 25 | @Inject(method = "", at = @At("RETURN")) 26 | private void reinit(List models, CallbackInfo ci) { 27 | this.models = CollectionHelper.fixed(this.models); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinAndMultipartModelSelector.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.client.model.json; 2 | 3 | import com.google.common.collect.Streams; 4 | import me.jellysquid.mods.hydrogen.common.state.StatePropertyPredicateHelper; 5 | import net.minecraft.block.Block; 6 | import net.minecraft.block.BlockState; 7 | import net.minecraft.client.render.model.json.AndMultipartModelSelector; 8 | import net.minecraft.client.render.model.json.MultipartModelSelector; 9 | import net.minecraft.state.StateManager; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Overwrite; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | 15 | import java.util.function.Predicate; 16 | import java.util.stream.Collectors; 17 | 18 | @Mixin(AndMultipartModelSelector.class) 19 | public class MixinAndMultipartModelSelector { 20 | @Shadow 21 | @Final 22 | private Iterable selectors; 23 | 24 | /** 25 | * @author JellySquid 26 | * @reason Flatten predicates 27 | */ 28 | @Overwrite 29 | public Predicate getPredicate(StateManager stateManager) { 30 | return StatePropertyPredicateHelper.allMatch(Streams.stream(this.selectors).map((multipartModelSelector) -> { 31 | return multipartModelSelector.getPredicate(stateManager); 32 | }).collect(Collectors.toList())); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinOrMultipartModelSelector.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.client.model.json; 2 | 3 | import com.google.common.collect.Streams; 4 | import me.jellysquid.mods.hydrogen.common.state.StatePropertyPredicateHelper; 5 | import me.jellysquid.mods.hydrogen.common.util.AnyPredicate; 6 | import net.minecraft.block.Block; 7 | import net.minecraft.block.BlockState; 8 | import net.minecraft.client.render.model.json.MultipartModelSelector; 9 | import net.minecraft.client.render.model.json.OrMultipartModelSelector; 10 | import net.minecraft.state.StateManager; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Overwrite; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | 16 | import java.util.List; 17 | import java.util.function.Predicate; 18 | import java.util.stream.Collectors; 19 | 20 | @Mixin(OrMultipartModelSelector.class) 21 | public class MixinOrMultipartModelSelector { 22 | @Shadow @Final private Iterable selectors; 23 | 24 | /** 25 | * @author JellySquid 26 | * @reason Flatten predicates 27 | */ 28 | @Overwrite 29 | public Predicate getPredicate(StateManager stateManager) { 30 | return StatePropertyPredicateHelper.anyMatch(Streams.stream(this.selectors).map((multipartModelSelector) -> { 31 | return multipartModelSelector.getPredicate(stateManager); 32 | }).collect(Collectors.toList())); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinSimpleMultipartModelSelector.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.client.model.json; 2 | 3 | import com.google.common.base.Splitter; 4 | import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchAny; 5 | import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchOne; 6 | import net.minecraft.block.Block; 7 | import net.minecraft.block.BlockState; 8 | import net.minecraft.client.render.model.json.SimpleMultipartModelSelector; 9 | import net.minecraft.state.StateManager; 10 | import net.minecraft.state.property.Property; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Overwrite; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | 16 | import java.util.List; 17 | import java.util.function.Predicate; 18 | import java.util.stream.Collectors; 19 | 20 | @SuppressWarnings("UnstableApiUsage") 21 | @Mixin(SimpleMultipartModelSelector.class) 22 | public class MixinSimpleMultipartModelSelector { 23 | @Shadow 24 | @Final 25 | private String key; 26 | 27 | @Shadow 28 | @Final 29 | private String valueString; 30 | 31 | @Shadow 32 | @Final 33 | private static Splitter VALUE_SPLITTER; 34 | 35 | /** 36 | * @author JellySquid 37 | * @reason De-duplication 38 | */ 39 | @Overwrite 40 | public Predicate getPredicate(StateManager stateManager) { 41 | Property property = stateManager.getProperty(this.key); 42 | 43 | if (property == null) { 44 | throw new RuntimeException(String.format("Unknown property '%s' on '%s'", this.key, stateManager.getOwner().toString())); 45 | } 46 | 47 | String valueString = this.valueString; 48 | boolean negate = !valueString.isEmpty() && valueString.charAt(0) == '!'; 49 | 50 | if (negate) { 51 | valueString = valueString.substring(1); 52 | } 53 | 54 | List split = VALUE_SPLITTER.splitToList(valueString); 55 | 56 | if (split.isEmpty()) { 57 | throw new RuntimeException(String.format("Empty value '%s' for property '%s' on '%s'", this.valueString, this.key, stateManager.getOwner().toString())); 58 | } 59 | 60 | Predicate predicate; 61 | 62 | if (split.size() == 1) { 63 | predicate = new SingleMatchOne(property, this.getPropertyValue(stateManager, property, valueString)); 64 | } else { 65 | predicate = SingleMatchAny.create(property, split.stream() 66 | .map(str -> this.getPropertyValue(stateManager, property, str)) 67 | .collect(Collectors.toList())); 68 | } 69 | 70 | return negate ? predicate.negate() : predicate; 71 | } 72 | 73 | private Object getPropertyValue(StateManager stateFactory, Property property, String valueString) { 74 | Object value = property.parse(valueString) 75 | .orElse(null); 76 | 77 | if (value == null) { 78 | throw new RuntimeException(String.format("Unknown value '%s' for property '%s' on '%s' in '%s'", 79 | valueString, this.key, stateFactory.getOwner().toString(), this.valueString)); 80 | } 81 | 82 | return value; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/nbt/MixinNbtCompound.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.nbt; 2 | 3 | import it.unimi.dsi.fastutil.objects.Object2ObjectMap; 4 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 5 | import net.minecraft.nbt.NbtCompound; 6 | import net.minecraft.nbt.NbtElement; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Mutable; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | import java.util.Map; 16 | 17 | @Mixin(NbtCompound.class) 18 | public class MixinNbtCompound { 19 | @Mutable 20 | @Shadow 21 | @Final 22 | private Map entries; 23 | 24 | @Inject(method = "(Ljava/util/Map;)V", at = @At("RETURN")) 25 | private void reinit(Map tags, CallbackInfo ci) { 26 | this.entries = tags instanceof Object2ObjectMap ? tags : new Object2ObjectOpenHashMap<>(tags); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/state/MixinState.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.state; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.google.common.collect.Table; 5 | import com.mojang.serialization.MapCodec; 6 | import me.jellysquid.mods.hydrogen.common.cache.StatePropertyTableCache; 7 | import me.jellysquid.mods.hydrogen.common.collections.FastImmutableTable; 8 | import me.jellysquid.mods.hydrogen.common.jvm.ClassConstructors; 9 | import net.minecraft.state.State; 10 | import net.minecraft.state.property.Property; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Mutable; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | 19 | import java.util.Map; 20 | 21 | @Mixin(State.class) 22 | public class MixinState { 23 | @Mutable 24 | @Shadow 25 | @Final 26 | private ImmutableMap, Comparable> entries; 27 | 28 | @Shadow 29 | private Table, Comparable, S> withTable; 30 | 31 | @Shadow 32 | @Final 33 | protected O owner; 34 | 35 | @Inject(method = "", at = @At("RETURN")) 36 | private void reinit(O owner, ImmutableMap, Comparable> entries, MapCodec mapCodec, CallbackInfo ci) { 37 | this.entries = ClassConstructors.createFastImmutableMap(this.entries); 38 | } 39 | 40 | @Inject(method = "createWithTable", at = @At("RETURN")) 41 | private void postCreateWithTable(Map, Comparable>, S> states, CallbackInfo ci) { 42 | this.withTable = new FastImmutableTable<>(this.withTable, StatePropertyTableCache.getTableCache(this.owner)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/me/jellysquid/mods/hydrogen/mixin/util/MixinIdentifier.java: -------------------------------------------------------------------------------- 1 | package me.jellysquid.mods.hydrogen.mixin.util; 2 | 3 | import me.jellysquid.mods.hydrogen.common.dedup.IdentifierCaches; 4 | import net.minecraft.util.Identifier; 5 | import org.spongepowered.asm.mixin.Final; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Mutable; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | @Mixin(Identifier.class) 14 | public class MixinIdentifier { 15 | @Mutable 16 | @Shadow 17 | @Final 18 | protected String namespace; 19 | 20 | @Mutable 21 | @Shadow 22 | @Final 23 | protected String path; 24 | 25 | @Inject(method = "([Ljava/lang/String;)V", at = @At("RETURN")) 26 | private void reinit(String[] id, CallbackInfo ci) { 27 | this.namespace = IdentifierCaches.NAMESPACES.deduplicate(this.namespace); 28 | this.path = IdentifierCaches.PATH.deduplicate(this.path); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/assets/hydrogen/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaffeineMC/hydrogen-fabric/95da36046c2f55617a76ebb8ea1e1f2a330330e5/src/main/resources/assets/hydrogen/icon.png -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "hydrogen", 4 | "version": "${version}", 5 | "name": "Hydrogen", 6 | "description": "A Minecraft mod designed to reduce the memory usage of the game.", 7 | "authors": [ 8 | "JellySquid" 9 | ], 10 | "contact": { 11 | "sources": "https://github.com/CaffeineMC/hydrogen-fabric", 12 | "issues": "https://github.com/CaffeineMC/hydrogen-fabric/issues" 13 | }, 14 | "license": "LGPL-3.0-only", 15 | "icon": "assets/hydrogen/icon.png", 16 | "environment": "*", 17 | "mixins": [ 18 | "hydrogen.mixins.json" 19 | ], 20 | "entrypoints": { 21 | "preLaunch": [ 22 | "me.jellysquid.mods.hydrogen.common.HydrogenPreLaunch" 23 | ], 24 | "client": [ 25 | "me.jellysquid.mods.hydrogen.client.HydrogenClient" 26 | ] 27 | }, 28 | "depends": { 29 | "fabricloader": ">=0.11.0", 30 | "fabric-resource-loader-v0": ">=0.4", 31 | "minecraft": ["1.17", "1.17.1"] 32 | }, 33 | "breaks": { 34 | "optifabric": "*" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/resources/hydrogen.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": "me.jellysquid.mods.hydrogen.mixin", 3 | "required": true, 4 | "compatibilityLevel": "JAVA_16", 5 | "injectors": { 6 | "defaultRequire": 1 7 | }, 8 | "client": [ 9 | "client.model.MixinBakedQuad", 10 | "client.model.MixinBasicBakedModel", 11 | "client.model.MixinModelLoader", 12 | "client.model.MixinMultipartBakedModel", 13 | "client.model.MixinWeightedBakedModel", 14 | "client.model.json.MixinAndMultipartModelSelector", 15 | "client.model.json.MixinOrMultipartModelSelector", 16 | "client.model.json.MixinSimpleMultipartModelSelector", 17 | "client.model.MixinModelIdentifier" 18 | ], 19 | "mixins": [ 20 | "chunk.MixinChunkSerializer", 21 | "chunk.MixinWorldChunk", 22 | "nbt.MixinNbtCompound", 23 | "state.MixinState", 24 | "util.MixinIdentifier" 25 | ] 26 | } --------------------------------------------------------------------------------