diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 00000000..b8db7758 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,58 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created +# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle + +name: TesseractAPI Nightly +on: + push: + branches: + - dev + pull_request: + branches: + - dev +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + - name: Setup Gradle Dependencies Cache + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle','**/gradle/wrapper/gradle-wrapper.properties','**/*.properties') }} + - name: Build with Gradle + uses: gradle/gradle-build-action@4137be6a8bf7d7133955359dbd952c0ca73b1021 + with: + arguments: build + # The USERNAME and TOKEN need to correspond to the credential environment variables used in + # the publishing section of your build.gradle + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: TesseractAPI JAR + path: build/libs + env: + USERNAME: ${{ github.actor }} + TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Cleanup Gradle Cache + # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions. + # Restoring these files from a GitHub Actions cache might cause problems for future builds. + run: | + rm -f ~/.gradle/caches/modules-2/modules-2.lock + rm -f ~/.gradle/caches/modules-2/gc.properties diff --git a/.gitignore b/.gitignore index d7973e18..acc4382d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ run # Files from Forge MDK forge*changelog.txt + +.vscode +mcmodsrepo/ diff --git a/build.gradle b/build.gradle index 6759e39b..3b621758 100644 --- a/build.gradle +++ b/build.gradle @@ -1,100 +1,208 @@ buildscript { repositories { + maven { url 'https://files.minecraftforge.net/maven' } + jcenter() + mavenCentral() + gradlePluginPortal() maven { - url "https://plugins.gradle.org/m2/" + name = 'parchment' + url = 'https://maven.parchmentmc.org' } } dependencies { - classpath "com.github.jengelman.gradle.plugins:shadow:5.2.0" + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1+', changing: true + classpath 'org.parchmentmc:librarian:1.+' } } - plugins { id 'maven-publish' - id 'java' + id 'com.github.johnrengelman.shadow' version '7.1.0' +} + +def isCI = System.getenv("GITHUB_ACTION") +def isRELEASE = System.getenv("GITHUB_RELEASE") +def gitHash() { + String hash = System.getenv("GITHUB_SHA") + if (hash != null) return hash.substring(0,8) + return "" } + +apply plugin: 'net.minecraftforge.gradle' +apply plugin: "eclipse" apply plugin: "com.github.johnrengelman.shadow" -//evaluationDependsOn(':version') +apply plugin: 'java' +apply plugin: 'org.parchmentmc.librarian.forgegradle' +archivesBaseName = 'TesseractAPI' +version = "${minecraft_version}-${mod_version}" +group = "com.github.gregtech-intergalactical" + +if (isCI) { + version = version + "-" + gitHash() +} -group = 'com.github.gregtech-intergalactical' // http://maven.apache.org/guides/mini/guide-naming-conventions.html -archivesBaseName = 'tesseract' +java.toolchain.languageVersion = JavaLanguageVersion.of(8) +configurations { + shadow +} +minecraft { + // The mappings can be changed at any time, and must be in the following format. + // snapshot_YYYYMMDD Snapshot are built nightly. + // stable_# Stables are built at the discretion of the MCP team. + // Use non-default mappings at your own risk. they may not always work. + // Simply re-run your setup task after changing the mappings to update your workspace. + mappings channel: 'parchment', version: '2021.10.17-1.16.5' + // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. + + // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + + // Default run configurations. + // These can be tweaked, removed, or duplicated as needed. + runs { + client { + workingDirectory project.file('run') + + // Recommended logging data for a userdev environment + property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + + // Recommended logging level for the console + property 'forge.logging.console.level', 'debug' + + mods { + tesseractapi { + source sourceSets.main + } + } + } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. + server { + workingDirectory project.file('run') -assemble.dependsOn shadowJar + // Recommended logging data for a userdev environment + property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' -configurations { - embed - deobf + // Recommended logging level for the console + property 'forge.logging.console.level', 'debug' - embed.extendsFrom(implementation) - testCompile.extendsFrom(shadow) -} + mods { + tesseractapi { + source sourceSets.main + } + } + } -shadowJar { - configurations = [project.configurations.embed] + data { + workingDirectory project.file('run') + + // Recommended logging data for a userdev environment + property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + + // Recommended logging level for the console + property 'forge.logging.console.level', 'debug' + + args '--mod', 'tesseract', '--all', '--output', '"' + rootProject.file('src/generated/resources/') + '"', + '--existing', '"' + sourceSets.main.resources.srcDirs[0] + '"' + + mods { + tesseractapi { + source sourceSets.main + } + } + } + } } + + // Deobfuscated jar; development purposes. -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -task deobfJar(type: ShadowJar) { - from sourceSets.main.output - configurations = [project.configurations.deobf] - classifier "dev" -} +//import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar repositories { jcenter() - maven { url = 'https://files.minecraftforge.net/maven' } +} +afterEvaluate { project -> + project.tasks.publishToMavenLocal { + onlyIf { + return rootProject.name == "${modid}" + } + } +} +task sourcesJar(type: Jar) { + from sourceSets.main.allSource + archiveBaseName.set(project.archivesBaseName) + archiveVersion.set("${project.version}") + archiveClassifier.set('sources') } +/*task javadocJar(type: Jar, dependsOn: javadoc) { + from javadoc.destinationDir + archiveClassifier.set('javadoc') +}*/ +shadowJar { + configurations = [project.configurations.shadow] + archiveClassifier.set('') +} +reobf { + shadowJar {} +} dependencies { - // Main dependencies: - // Declare here your dependencies here with: - // 'shadow' if the jar shouldn't be included in the fat jar, - // 'implementation' if the jar should be included in the jar - shadow 'org.apache.commons:commons-collections4:4.4' - shadow 'it.unimi.dsi:fastutil:8.2.1' - - // Test dependencies: - testImplementation 'junit:junit:4.11' - - // Embedded dependencies: - // Those will be included in the fat jar. - // You will likely not edit those dependencies. - if (rootProject.name == "tesseract") { - embed project(path: ':forge', configuration: 'archives') - deobf project(path: ':forge', configuration: 'deobf') - } else { - embed project(path: ':tesseract:forge', configuration: 'archives') - deobf project(path: ':tesseract:forge', configuration: 'deobf') - } - - //embed project(path: ':fabric', configuration: 'archives') TODO coming soon(tm). + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + testImplementation('junit:junit:4.11') } +afterEvaluate { project -> + project.tasks.publishToMavenLocal { + onlyIf { + return rootProject.name == "${modid}" + } + } +} +if (isCI) { + jar.finalizedBy('reobfJar') + println("In CI mode") +} jar { zip64 true + manifest { + attributes([ + "Specification-Title": project.name, + "Specification-Vendor": "GregTech Intergalactical", + "Specification-Version": project.version, + "Implementation-Title": project.name, + "Implementation-Version": project.version, + "Implementation-Vendor": "GregTech Intergalactical", + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") + ]) + } } -// Maven publishing: - -static def configurePublishingRepositories(repositoryHandler) { - if (System.env.MAVEN_URL) { - repositoryHandler.maven { - url = System.env.MAVEN_URL - - if (System.env.MAVEN_USERNAME && System.env.MAVEN_PASSWORD) { - authentication { - basic(BasicAuthentication) - } - +artifacts { + archives jar, shadowJar, sourcesJar//, javadocJar +} +publishing { + tasks.publish.dependsOn 'build', 'reobfJar' + publications { + mavenJava(MavenPublication) { + //artifactId = archivesBaseName + artifact shadowJar + } + } + repositories { + if (isCI && isRELEASE) { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/GregTech-Intergalactical/TesseractAPI" credentials { - username = System.env.USERNAME - password = System.env.PASSWORD + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") } } + } else { + maven { url "file:///${project.projectDir}/mcmodsrepo"} } } -} \ No newline at end of file + //repositories { + // rootProject.configurePublishingRepositories(delegate) + //} +} + diff --git a/forge/build.gradle b/forge/build.gradle index 3a07d958..96c126a6 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -18,9 +18,14 @@ plugins { apply plugin: 'net.minecraftforge.gradle' apply plugin: "com.github.johnrengelman.shadow" -archivesBaseName = 'tesseract-forge' -version = rootProject.version -group = rootProject.group + +apply plugin: 'java' + +archivesBaseName = 'tesseractforge' +version = "0.0.1" +group = "com.github.gregtech-intergalactical" + +sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. minecraft { // The mappings can be changed at any time, and must be in the following format. @@ -46,7 +51,7 @@ minecraft { property 'forge.logging.console.level', 'debug' mods { - tesseract { + tesseractforge { source sourceSets.main } } @@ -62,7 +67,7 @@ minecraft { property 'forge.logging.console.level', 'debug' mods { - tesseract { + tesseractforge { source sourceSets.main } } @@ -77,11 +82,11 @@ minecraft { // Recommended logging level for the console property 'forge.logging.console.level', 'debug' - args '--mod', 'tesseract', '--all', '--output', '"' + rootProject.file('src/generated/resources/') + '"', + args '--mod', 'tesseractforge', '--all', '--output', '"' + rootProject.file('src/generated/resources/') + '"', '--existing', '"' + sourceSets.main.resources.srcDirs[0] + '"' mods { - tesseract { + tesseractforge { source sourceSets.main } } @@ -103,30 +108,34 @@ shadowJar { // Deobfuscated jar; development purposes. import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -task deobfJar(type: ShadowJar, dependsOn: 'classes') { +task deobfJar(type: ShadowJar) { from sourceSets.main.output configurations = [project.configurations.embed] - classifier "dev" + classifier "deobf" + //dependsOn gradle.includedBuild("tesseract").task(":deobfJar") } -// Sources jar; development purposes. -task sourcesJar(type: Jar) { +task sourcesJar(type: ShadowJar, dependsOn: classes) { + description = 'Creates a JAR containing the source code.' + classifier 'sources' from sourceSets.main.allSource - classifier "sources" +} + +artifacts { + archives sourcesJar + archives deobfJar } repositories { jcenter() } + dependencies { minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" - if (rootProject.name == "tesseract") { - embed project(path: ":") - } else { - embed project(path: ":tesseract") - } + embed project(':tesseract')//(group: 'com.github.gregtech-intergalactical', name: 'tesseract') } + publishing { publications { forge(MavenPublication) { @@ -140,6 +149,6 @@ publishing { //} } -artifacts { - deobf deobfJar +jar { + zip64 true } \ No newline at end of file diff --git a/forge/gradle/wrapper/gradle-wrapper.jar b/forge/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..7a3265ee Binary files /dev/null and b/forge/gradle/wrapper/gradle-wrapper.jar differ diff --git a/forge/gradle/wrapper/gradle-wrapper.properties b/forge/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..d4ac5175 --- /dev/null +++ b/forge/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jul 02 03:20:41 CEST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/forge/gradlew b/forge/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/forge/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/forge/settings.gradle b/forge/settings.gradle new file mode 100644 index 00000000..9607644d --- /dev/null +++ b/forge/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'tesseractforge' + diff --git a/forge/src/main/java/tesseract/Tesseract.java b/forge/src/main/java/tesseract/Tesseract.java deleted file mode 100644 index 2fb832ac..00000000 --- a/forge/src/main/java/tesseract/Tesseract.java +++ /dev/null @@ -1,52 +0,0 @@ -package tesseract; - -import net.minecraft.item.ItemStack; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fluids.FluidStack; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; -import net.minecraftforge.fml.event.server.FMLServerAboutToStartEvent; -import net.minecraftforge.fml.event.server.FMLServerStartingEvent; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import tesseract.api.GraphWrapper; -import tesseract.api.gt.IGTCable; -import tesseract.api.gt.IGTNode; -import tesseract.api.fluid.IFluidNode; -import tesseract.api.fluid.IFluidPipe; -import tesseract.api.fe.FEController; -import tesseract.api.fe.IFECable; -import tesseract.api.fe.IFENode; -import tesseract.api.item.IItemNode; -import tesseract.api.item.IItemPipe; -import tesseract.api.item.ItemController; -import tesseract.controller.Energy; -import tesseract.controller.Fluid; - -@Mod(Tesseract.API_ID) -@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD) -public class Tesseract { - - public static final String API_ID = "tesseract"; - public static final String API_NAME = "Tesseract API"; - public static final String VERSION = "0.0.1"; - public static final String DEPENDS = ""; - - public static GraphWrapper FE_ENERGY; - public static GraphWrapper GT_ENERGY; - public static GraphWrapper> FLUID; - public static GraphWrapper> ITEM; - - public Tesseract() { - FMLJavaModLoadingContext.get().getModEventBus().register(this); - MinecraftForge.EVENT_BUS.register(this); - } - - @SubscribeEvent - public void init(FMLServerAboutToStartEvent e) { - FE_ENERGY = new GraphWrapper<>(FEController::new); - GT_ENERGY = new GraphWrapper<>(Energy::new); - FLUID = new GraphWrapper<>(Fluid::new); - ITEM = new GraphWrapper<>(ItemController::new); - } -} diff --git a/forge/src/main/java/tesseract/controller/Energy.java b/forge/src/main/java/tesseract/controller/Energy.java deleted file mode 100644 index 1959955e..00000000 --- a/forge/src/main/java/tesseract/controller/Energy.java +++ /dev/null @@ -1,33 +0,0 @@ -package tesseract.controller; - -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.Explosion; -import tesseract.api.gt.GTController; - -// TODO: Make explosions depend on voltage, amp -public class Energy extends GTController { - - /** - * Creates instance of the tesseract.controller. - * - * @param dim The dimension id. - */ - public Energy(int dim) { - super(dim); - } - - @Override - public void onNodeOverVoltage(int dim, long pos, int voltage) { - Utils.getServerWorld(dim).ifPresent(w -> Utils.createExplosion(w, BlockPos.fromLong(pos), 4.0F, Explosion.Mode.BREAK)); - } - - @Override - public void onCableOverAmperage(int dim, long pos, int amperage) { - Utils.getServerWorld(dim).ifPresent(w -> Utils.createFireAround(w, BlockPos.fromLong(pos))); - } - - @Override - public void onCableOverVoltage(int dim, long pos, int voltage) { - Utils.getServerWorld(dim).ifPresent(w -> Utils.createFireAround(w, BlockPos.fromLong(pos))); - } -} diff --git a/forge/src/main/java/tesseract/controller/Fluid.java b/forge/src/main/java/tesseract/controller/Fluid.java deleted file mode 100644 index 7a4db565..00000000 --- a/forge/src/main/java/tesseract/controller/Fluid.java +++ /dev/null @@ -1,45 +0,0 @@ -package tesseract.controller; - -import net.minecraft.block.Blocks; -import net.minecraft.fluid.Fluids; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.Explosion; -import tesseract.api.fluid.FluidController; -import tesseract.api.fluid.FluidData; -import tesseract.api.fluid.IFluidNode; - -import javax.annotation.Nonnull; - -// TODO: Make explosions depend on pressure, capacity, temperature -public class Fluid extends FluidController> { - - /** - * Creates instance of the tesseract.controller. - * - * @param dim The dimension id. - */ - public Fluid(int dim) { - super(dim); - } - - @Override - public void onPipeOverPressure(int dim, long pos, int pressure) { - Utils.getServerWorld(dim).ifPresent(w -> Utils.createExplosion(w, BlockPos.fromLong(pos), 4.0F, Explosion.Mode.BREAK)); - } - - @Override - public void onPipeOverCapacity(int dim, long pos, int capacity) { - Utils.getServerWorld(dim).ifPresent(w -> Utils.createExplosion(w, BlockPos.fromLong(pos), 1.0F, Explosion.Mode.NONE)); - } - - @Override - public void onPipeOverTemp(int dim, long pos, int temperature) { - Utils.getServerWorld(dim).ifPresent(w -> w.setBlockState(BlockPos.fromLong(pos), temperature >= Fluids.LAVA.getAttributes().getTemperature() ? Blocks.LAVA.getDefaultState() : Blocks.FIRE.getDefaultState())); - } - - @Override - public void onPipeGasLeak(int dim, long pos, @Nonnull FluidData fluid) { - T resource = fluid.getStack(); - // resource.setAmount((int)(resource.getAmount() * AntimatterConfig.GAMEPLAY.PIPE_LEAK)); - } -} diff --git a/forge/src/main/java/tesseract/controller/Utils.java b/forge/src/main/java/tesseract/controller/Utils.java deleted file mode 100644 index 25a5cc3f..00000000 --- a/forge/src/main/java/tesseract/controller/Utils.java +++ /dev/null @@ -1,37 +0,0 @@ -package tesseract.controller; - -import net.minecraft.block.Blocks; -import net.minecraft.particles.ParticleTypes; -import net.minecraft.util.Direction; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.Explosion; -import net.minecraft.world.dimension.DimensionType; -import net.minecraft.world.server.ServerWorld; -import net.minecraftforge.fml.server.ServerLifecycleHooks; - -import java.util.Optional; - -public class Utils { - public static Optional getServerWorld(int dimension) { - DimensionType type = DimensionType.getById(dimension); - if (type == null) return Optional.empty(); - return Optional.of(ServerLifecycleHooks.getCurrentServer().getWorld(type)); - } - - public static void createExplosion(ServerWorld world, BlockPos pos, float explosionRadius, Explosion.Mode modeIn) { - world.createExplosion(null, pos.getX(), pos.getY() + 0.0625D, pos.getZ(), explosionRadius, true, modeIn); - world.spawnParticle(ParticleTypes.SMOKE, pos.getX(), pos.getY() + 0.5D, pos.getZ(), 1, 0, 0, 0, 0.0D); - } - - public static void createFireAround(ServerWorld world, BlockPos pos) { - boolean fired = false; - for (Direction side : Direction.values()) { - BlockPos offset = pos.offset(side); - if (world.getBlockState(offset) == Blocks.AIR.getDefaultState()) { - world.setBlockState(offset, Blocks.FIRE.getDefaultState()); - fired = true; - } - } - if (!fired) world.setBlockState(pos, Blocks.FIRE.getDefaultState()); - } -} diff --git a/gradle.properties b/gradle.properties index 784899a6..535bffbc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,3 +2,12 @@ # This is required to provide enough memory for the Minecraft decompilation process. org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false + +mod_version=0.1 + +mappings_version=20210309-1.16.5 +minecraft_version=1.16.5 +forge_version=36.2.29 +jei_version=1.16.4:7.6.1.71 + +modid=TesseractAPI diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5028f28f..e750102e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 687b0c33..2c0ddb45 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,14 +1,2 @@ -pluginManagement { - repositories { - gradlePluginPortal() +rootProject.name = "${modid}" - maven { url = 'https://files.axelandre42.ovh/maven/' } - maven { url = 'https://files.minecraftforge.net/maven/' } - jcenter() - mavenCentral() - } -} - -rootProject.name = 'tesseract' -include ':version' -include ':forge' diff --git a/src/main/java/tesseract/Tesseract.java b/src/main/java/tesseract/Tesseract.java new file mode 100644 index 00000000..bebd6bb8 --- /dev/null +++ b/src/main/java/tesseract/Tesseract.java @@ -0,0 +1,94 @@ +package tesseract; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.world.IWorld; +import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.world.WorldEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.event.server.FMLServerStoppedEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import tesseract.api.GraphWrapper; +import tesseract.api.capability.TesseractGTCapability; +import tesseract.api.fluid.FluidTransaction; +import tesseract.api.fluid.IFluidNode; +import tesseract.api.fluid.IFluidPipe; +import tesseract.api.gt.GTTransaction; +import tesseract.api.gt.IGTCable; +import tesseract.api.gt.IGTNode; +import tesseract.api.item.IItemNode; +import tesseract.api.item.IItemPipe; +import tesseract.api.item.ItemController; +import tesseract.api.item.ItemTransaction; +import tesseract.controller.Energy; +import tesseract.controller.Fluid; + +import java.util.Set; +import java.util.function.Consumer; + +@Mod(Tesseract.API_ID) +//@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD) +public class Tesseract { + + public static final String API_ID = "tesseractapi"; + public static final String API_NAME = "Tesseract API"; + public static final String VERSION = "0.0.1"; + public static final String DEPENDS = ""; + + public static final Logger LOGGER = LogManager.getLogger(API_ID); + + private final static Set firstTick = new ObjectOpenHashSet<>(); + //public static GraphWrapper FE_ENERGY = new GraphWrapper<>(FEController::new); + public static GraphWrapper GT_ENERGY = new GraphWrapper<>(Energy::new, IGTNode.GT_GETTER); + public static GraphWrapper FLUID = new GraphWrapper<>(Fluid::new, IFluidNode.GETTER); + public static GraphWrapper ITEM = new GraphWrapper<>(ItemController::new, IItemNode.GETTER); + + public static final int HEALTH_CHECK_TIME = 1000; + + public Tesseract() { + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); + MinecraftForge.EVENT_BUS.addListener(this::serverStoppedEvent); + MinecraftForge.EVENT_BUS.addListener(this::worldUnloadEvent); + MinecraftForge.EVENT_BUS.addListener(this::onServerTick); + } + + public static boolean hadFirstTick(IWorld world) { + return firstTick.contains(world); + } + + public void commonSetup(FMLCommonSetupEvent event) { + TesseractGTCapability.register(); + } + + public void serverStoppedEvent(FMLServerStoppedEvent e) { + firstTick.clear(); + //FE_ENERGY.clear(); + GraphWrapper.getWrappers().forEach(GraphWrapper::clear); + } + + public void worldUnloadEvent(WorldEvent.Unload e) { + if (!(e.getWorld() instanceof World) || ((World) e.getWorld()).isClientSide) return; + //FE_ENERGY.removeWorld((World) e.getWorld()); + GraphWrapper.getWrappers().forEach(g -> g.removeWorld((World)e.getWorld())); + firstTick.remove(e.getWorld()); + } + + public void onServerTick(TickEvent.WorldTickEvent event) { + if (event.side.isClient()) return; + World dim = event.world; + if (!hadFirstTick(dim)) { + firstTick.add(event.world); + GraphWrapper.getWrappers().forEach(t -> t.onFirstTick(dim)); + } + if (event.phase == TickEvent.Phase.START) { + GraphWrapper.getWrappers().forEach(t -> t.tick(dim)); + } + if (HEALTH_CHECK_TIME > 0 && event.world.getGameTime() % HEALTH_CHECK_TIME == 0) { + GraphWrapper.getWrappers().forEach(GraphWrapper::healthCheck); + } + } +} diff --git a/src/main/java/tesseract/api/Consumer.java b/src/main/java/tesseract/api/Consumer.java index 5b4f78b6..ba526835 100644 --- a/src/main/java/tesseract/api/Consumer.java +++ b/src/main/java/tesseract/api/Consumer.java @@ -1,22 +1,24 @@ package tesseract.api; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import tesseract.graph.Path; +import static java.lang.Integer.compare; import java.util.Comparator; -import static java.lang.Integer.compare; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +import tesseract.api.capability.TesseractBaseCapability; +import tesseract.graph.Path; /** * A class that acts as a wrapper for a node component. */ -abstract public class Consumer { +abstract public class Consumer { protected final N node; protected final ConnectionType connection; - protected Long2ObjectMap full; - protected Long2ObjectMap cross; + protected Long2ObjectMap full = Long2ObjectMaps.emptyMap(); + protected Long2ObjectMap cross = Long2ObjectMaps.emptyMap(); protected int distance; // Way of the sorting by the priority level and the distance to the node @@ -28,16 +30,32 @@ abstract public class Consumer { * @param node The node instance. * @param path The path information. */ - protected Consumer(N node, Path path) { + protected Consumer(N node, N producer, Path path) { this.node = node; if (path != null) { - full = path.getFull(); + full = path.getFull(); cross = path.getCross(); } + int fullSize = full.size(); + if (producer instanceof TesseractBaseCapability) { + TesseractBaseCapability cap = (TesseractBaseCapability) producer; + long pos = cap.tile.getBlockPos().asLong(); + if (full.size() == 0) { + full = Long2ObjectMaps.singleton(pos, (C) cap.tile); + } else { + full.put(pos, (C) cap.tile); + } + if (cross.size() == 0) { + cross = Long2ObjectMaps.singleton(pos, (C) cap.tile); + } else { + cross.put(pos, (C) cap.tile); + } + } + int crossSize = cross.size(); - if (cross == null || cross.size() == 0) { - connection = (full == null) ? ConnectionType.ADJACENT : ConnectionType.SINGLE; + if (crossSize == 0) { + connection = (fullSize == 0) ? ConnectionType.ADJACENT : ConnectionType.SINGLE; } else { connection = ConnectionType.VARIATE; } @@ -53,6 +71,7 @@ public void init() { onConnectorCatch(connector); } } + } /** @@ -83,7 +102,6 @@ public ConnectionType getConnection() { public Long2ObjectMap getCross() { return cross; } - /** * @return Gets the full path of connectors. */ diff --git a/src/main/java/tesseract/api/Controller.java b/src/main/java/tesseract/api/Controller.java index 1311f1c0..3cb4046f 100644 --- a/src/main/java/tesseract/api/Controller.java +++ b/src/main/java/tesseract/api/Controller.java @@ -1,24 +1,29 @@ package tesseract.api; +import net.minecraft.world.World; +import tesseract.graph.Graph; import tesseract.graph.Group; import tesseract.graph.INode; + /** * Class acts as a controller in the group of some components. */ -abstract public class Controller implements ITickingController { +abstract public class Controller implements ITickingController { protected int tick; - protected final int dim; - protected Group group; + protected final World dim; + protected Group group; + protected final Graph.INodeGetter getter; /** * Creates instance of the controller. * - * @param dim The dimension id. + * @param supplier The world. */ - protected Controller(int dim) { - this.dim = dim; + protected Controller(World supplier, Graph.INodeGetter getter) { + this.dim = supplier; + this.getter = getter; } /** @@ -26,9 +31,8 @@ protected Controller(int dim) { * * @param container The group this controller handles. */ - @SuppressWarnings("unchecked") - public Controller set(INode container) { - this.group = (Group) container; + public Controller set(INode container) { + this.group = (Group) container; return this; } @@ -42,9 +46,13 @@ public void tick() { onFrame(); } } - /** * Frame handler, which executes each second. */ protected abstract void onFrame(); -} + + @Override + public World getWorld() { + return this.dim; + } +} \ No newline at end of file diff --git a/src/main/java/tesseract/api/GraphWrapper.java b/src/main/java/tesseract/api/GraphWrapper.java index 6797edde..e43ee343 100644 --- a/src/main/java/tesseract/api/GraphWrapper.java +++ b/src/main/java/tesseract/api/GraphWrapper.java @@ -1,55 +1,93 @@ package tesseract.api; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import tesseract.graph.*; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.*; +import net.minecraft.util.Direction; +import net.minecraft.world.IWorld; +import net.minecraft.world.World; +import net.minecraftforge.common.util.LazyOptional; +import tesseract.Tesseract; +import tesseract.graph.Cache; +import tesseract.graph.Graph; +import tesseract.graph.Graph.INodeGetter; +import tesseract.graph.Group; +import tesseract.graph.NodeCache; +import tesseract.util.Pos; -import java.util.function.IntFunction; +import javax.annotation.Nonnull; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Consumer; -public class GraphWrapper { +public class GraphWrapper { - protected final Int2ObjectMap> graph = new Int2ObjectOpenHashMap<>(); - protected final IntFunction> supplier; + private static final ObjectSet> ALL_WRAPPERS = new ObjectOpenHashSet<>(); + + protected final Object2ObjectMap> graph = new Object2ObjectOpenHashMap<>(); + protected final BiFunction, Controller> supplier; + protected final ICapabilityGetter getter; + private final Map PENDING_NODES = new Object2ObjectOpenHashMap<>(); /** * Creates a graph wrapper. * * @param supplier The default controller supplier. */ - public GraphWrapper(IntFunction> supplier) { + public GraphWrapper(BiFunction, Controller> supplier, ICapabilityGetter getter) { this.supplier = supplier; + this.getter = getter; + ALL_WRAPPERS.add(this); } /** * Creates an instance of a class for a given node. * - * @param dim The dimension id where the node will be added. - * @param pos The position at which the node will be added. - * @param node The node object. + * @param dim The dimension id where the node will be added. + * @param pos The position at which the node will be added. */ - public void registerNode(int dim, long pos, N node) { - getGraph(dim).addNode(pos, new Cache<>(node), supplier.apply(dim)); - } + /*public void registerNode(IWorld dim, long pos, Direction side, BiFunction node) { + if (dim.isClientSide()) + return; + getGraph(dim).addNode(pos, node, side, () -> supplier.apply(dim instanceof World ? ((World) dim) : null), + Tesseract.hadFirstTick(dim)); + }*/ /** - * Creates an instance of a class for a given connector. + * Registers a connector into Tesseract. * - * @param dim The dimension id where the node will be added. - * @param pos The position at which the node will be added. + * @param dim The dimension id where the node will be added. + * @param pos The position at which the node will be added. * @param connector The connector object. */ - public void registerConnector(int dim, long pos, C connector) { - getGraph(dim).addConnector(pos, new Cache<>(connector), supplier.apply(dim)); + public void registerConnector(World dim, long pos, C connector, boolean regular) { + if (dim.isClientSide()) + return; + getGraph(dim).addConnector(pos, new Cache<>(connector)); + if (!Tesseract.hadFirstTick(dim)) { + PENDING_NODES.computeIfAbsent(dim, d -> new LongOpenHashSet()).add(pos); + } else { + addNodes(dim, pos); + } + } + + public void blockUpdate(World dim, long connector, long node) { + if (dim.isClientSide()) return; + update(dim, node, Pos.subToDir(connector, node), false); } /** - * Gets the graph for the type and dimension and will be instantiated if it does not already exist. + * Gets the graph for the type and dimension and will be instantiated if it does + * not already exist. * * @param dim The dimension id. * @return The graph instance for the world. */ - public Graph getGraph(int dim) { - return graph.computeIfAbsent(dim, k -> new Graph<>()); + public Graph getGraph(IWorld dim) { + assert !dim.isClientSide(); + INodeGetter get = (a,b,c) -> getter.get((World)dim,a,b,c); + return graph.computeIfAbsent(dim, k -> new Graph<>(() -> supplier.apply((World) dim, get))); } /** @@ -59,18 +97,166 @@ public Graph getGraph(int dim) { * @param pos The position at which the electric component is exist. * @return The controller object. (Can be null) */ - public ITickingController getController(int dim, long pos) { - Group group = getGraph(dim).getGroupAt(pos); - return group != null ? group.getController() : null; + @Nonnull + public ITickingController getController(World dim, long pos) { + if (dim.isClientSide()) { + throw new IllegalStateException("Call to GraphWrapper::getController on client side!"); + } + Group group = getGraph(dim).getGroupAt(pos); + INodeGetter get = (a, b, c) -> getter.get((World) dim, a, b, c); + + return group != null ? group.getController() : supplier.apply(dim, get); + } + + /** + * Primary update method in Tesseract, receiving capability invalidations and block updates. + * + * @param pos the node position. + */ + private void update(World dim, long pos, @Nonnull Direction side, boolean isInvalidate) { + //offset to the connector. + long cPos = Pos.offset(pos, side); + Graph graph = getGraph(dim); + Group group = graph.getGroupAt(cPos); + if (group == null) return; + //only update nodes + Cache cCache = group.getConnector(cPos); + if (cCache == null) { + NodeCache nodeCache = group.getNodes().get(cPos); + if (nodeCache == null) return; + } + NodeCache cache = group.getNodes().get(pos); + INodeGetter get = (a, b, c) -> getter.get(dim, a, b, c); + if (cache == null) { + cache = new NodeCache<>(pos, get, (a, b) -> this.validate(graph, a, b), (a, b) -> this.update(dim, b, a, true)); + graph.addNode(pos, cache); + } else { + if (isInvalidate) { + if (cache.updateSide(side)) { + group.getController().change(); + return; + } + } + updateNode(graph, pos); + } + } + + /** + * Adds a node to the graph at the specified position. + * + * @param pos The position at which the node will be added. + */ + public void addNodes(World dim, long pos) { + Graph graph = getGraph(dim); + INodeGetter get = (a, b, c) -> getter.get(dim, a, b, c); + for (Direction dir : Graph.DIRECTIONS) { + final long nodePos = Pos.offset(pos, dir); + NodeCache cache = new NodeCache<>(nodePos, get, (a, b) -> this.validate(graph, a, b), (a, b) -> this.update(dim, b, a, true)); + graph.addNode(nodePos, cache); + } + } + + + public void onFirstTick(World dim) { + LongSet set = PENDING_NODES.remove(dim); + if (set != null) set.forEach((Consumer) l -> this.addNodes(dim, l)); + } + + + private void updateNode(Graph graph, long nodePos) { + Group group = graph.getGroupAt(nodePos); + if (group == null) { + return; + } + NodeCache cache = group.getNodes().get(nodePos); + if (cache == null) return; + int count = cache.count(); + boolean ok = updateNodeSides(cache); + if ((cache.count() != count) || cache.count() == 0) { + graph.removeAt(nodePos); + if (ok) { + graph.addNode(nodePos, cache); + } + } else { + group.getController().change(); + } } + /** + * Removes an entry from the Group, potentially splitting it if needed. By + * calling this function, the caller asserts that this group contains the + * specified position; the function may misbehave if the group does not actually + * contain the specified position. + * + * @param pos The position of the entry to remove. + */ + private boolean removeAt(Graph graph, long pos) { + Group gr = graph.getGroupAt(pos); + if (gr == null) return false; + boolean ok = graph.removeAt(pos); + if (ok) { + for (Direction dir : Graph.DIRECTIONS) { + updateNode(graph, Pos.offset(pos, dir)); + } + } + return ok; + } + + private boolean updateNodeSides(NodeCache node) { + for (int i = 0; i < Graph.DIRECTIONS.length; i++) { + node.updateSide(Graph.DIRECTIONS[i]); + } + return node.count() > 0; + } + + + boolean validate(Graph graph, Direction side, long pos) { + Group group = graph.getGroupAt(Pos.offset(pos, side)); + if (group == null) return false; + Cache conn = group.getConnector(Pos.offset(pos, side)); + if (conn != null) { + return conn.value().validate(side.getOpposite()); + } + //NodeCache cache = group.getNodes().get(Pos.offset(pos, side)); + return false; + } + + /** * Removes an instance of a class at the given position. * * @param dim The dimension id where the electric component will be added. * @param pos The position at which the electric component will be added. */ - public void remove(int dim, long pos) { - getGraph(dim).removeAt(pos); + public boolean remove(World dim, long pos) { + if (dim.isClientSide()) + return false; + return removeAt(getGraph(dim), pos); + } + + public void tick(World dim) { + Graph g = graph.get(dim); + if (g != null) + g.getGroups().forEach((pos, gr) -> gr.getController().tick()); + } + + public static Set> getWrappers() { + return ObjectSets.unmodifiable(ALL_WRAPPERS); + } + + public void removeWorld(World world) { + this.graph.remove(world); + } + + public void clear() { + this.graph.clear(); + } + + public void healthCheck() { + this.graph.values().forEach(v -> v.getGroups().values().forEach(Group::healthCheck)); + } + + public interface ICapabilityGetter { + T get(World level, long pos, Direction capSide, Runnable capCallback); } } diff --git a/src/main/java/tesseract/api/IConnectable.java b/src/main/java/tesseract/api/IConnectable.java index e1604a77..b263abea 100644 --- a/src/main/java/tesseract/api/IConnectable.java +++ b/src/main/java/tesseract/api/IConnectable.java @@ -1,6 +1,7 @@ package tesseract.api; -import tesseract.util.Dir; +import net.minecraft.util.Direction; +import tesseract.util.Pos; /** * A simple interface for representing connectable objects. @@ -11,5 +12,15 @@ public interface IConnectable { * @param direction The direction vector. * @return True if connect to the direction, false otherwise. */ - boolean connects(Dir direction); + boolean connects(Direction direction); + + boolean validate(Direction dir); + + default long traverse(long pos, Direction dir) { + return Pos.offset(pos, dir); + } + + default boolean path() { + return false; + } } diff --git a/src/main/java/tesseract/api/ITickHost.java b/src/main/java/tesseract/api/ITickHost.java deleted file mode 100644 index f1d2addf..00000000 --- a/src/main/java/tesseract/api/ITickHost.java +++ /dev/null @@ -1,15 +0,0 @@ -package tesseract.api; - -/** - * Represents a tile entity providing server ticks for the group controller - */ -public interface ITickHost { - - /** - * Set new controller pointer (or null). - * If the host already contains new non-null controller, then don't reset it to null. - * @param oldController The previous controller node. - * @param newController The new controller node. - */ - void reset(ITickingController oldController, ITickingController newController); -} diff --git a/src/main/java/tesseract/api/ITickingController.java b/src/main/java/tesseract/api/ITickingController.java index cbf334c9..00bb0756 100644 --- a/src/main/java/tesseract/api/ITickingController.java +++ b/src/main/java/tesseract/api/ITickingController.java @@ -1,11 +1,20 @@ package tesseract.api; +import net.minecraft.util.Direction; +import net.minecraft.world.World; +import tesseract.api.capability.ITransactionModifier; +import tesseract.graph.Graph; import tesseract.graph.INode; +import javax.annotation.Nonnull; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + /** * Interface abstracting ticking behaviour for the groups in the graph. */ -public interface ITickingController { +public interface ITickingController { /** * Server tick handler. @@ -19,13 +28,29 @@ public interface ITickingController { /** * Creates new controller for split group. + * * @param group New group. * @return New controller for the group. */ - ITickingController clone(INode group); + ITickingController clone(INode group); /** * @return To get simple things like a some information. */ - String[] getInfo(); + void getInfo(long pos, @Nonnull List list); + + /** + * Core method of Tesseract. Inserts an object into this pipe. + * @param producerPos the position of the producer + * @param side the side at which the object was inserted into the pipe. + * @param transaction the transaction object. + */ + void insert(long producerPos, Direction side, T transaction, ITransactionModifier modifier); + + /** + * Returns the active world for this ticking controller. + * + * @return the world object. + */ + World getWorld(); } diff --git a/src/main/java/tesseract/api/Transaction.java b/src/main/java/tesseract/api/Transaction.java new file mode 100644 index 00000000..9baa3e34 --- /dev/null +++ b/src/main/java/tesseract/api/Transaction.java @@ -0,0 +1,89 @@ +package tesseract.api; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; + +public abstract class Transaction { + private final ObjectArrayList> onCommit; + private final ObjectArrayList transmitted; + private final Consumer consumer; + private boolean cancelled; + + public Transaction(final Consumer consumed) { + this.transmitted = new ObjectArrayList<>(1); + this.onCommit = new ObjectArrayList<>(1); + this.consumer = consumed; + this.cancelled = false; + } + + protected T addData(T t) { + if (cancelled) return t; + this.transmitted.add(t); + return t; + } + + public T getLast() { + if (transmitted.size() == 0) throw new IllegalStateException("call to Transaction::getLast without data"); + return transmitted.get(transmitted.size()-1); + } + + public Iterable getOffset(int j) { + return () ->{ + Iterator t = this.transmitted.iterator(); + for (int i = 0; i < j; i++) { + t.next(); + } + return t; + }; + } + + public List getData() { + return cancelled ? Collections.emptyList() : transmitted; + } + + public void onCommit(Consumer consumer) { + if (cancelled) return; + this.onCommit.ensureCapacity(transmitted.size()); + this.onCommit.add(transmitted.size() - 1, consumer); + } + + public void pushCallback(Consumer consumer) { + if (cancelled || this.onCommit.size() == 0) return; + Consumer current = this.onCommit.get(this.onCommit.size()-1); + if (current != null) { + this.onCommit.add(this.onCommit.size()-1, current.andThen(consumer)); + } + } + + public void pushCallback(Consumer consumer, int j) { + if (cancelled || this.onCommit.size() == 0) return; + Consumer current = this.onCommit.get(j); + if (current != null) { + this.onCommit.add(j, current.andThen(consumer)); + } + } + + + public void cancel() { + this.cancelled = true; + } + + public void commit() { + if (cancelled) return; + for (int i = 0; i < transmitted.size(); i++) { + if (onCommit.get(i) != null) { + onCommit.get(i).accept(transmitted.get(i)); + } + this.consumer.accept(transmitted.get(i)); + } + } + + public abstract boolean isValid(); + + public abstract boolean canContinue(); + +} diff --git a/src/main/java/tesseract/api/capability/ITransactionModifier.java b/src/main/java/tesseract/api/capability/ITransactionModifier.java new file mode 100644 index 00000000..6a2c2dd2 --- /dev/null +++ b/src/main/java/tesseract/api/capability/ITransactionModifier.java @@ -0,0 +1,11 @@ +package tesseract.api.capability; + + +import net.minecraft.util.Direction; + +@FunctionalInterface +public interface ITransactionModifier { + void modify(Object stack, Direction in, Direction out, boolean simulate); + + ITransactionModifier EMPTY = (a,b,c,d) -> {}; +} diff --git a/src/main/java/tesseract/api/capability/TesseractBaseCapability.java b/src/main/java/tesseract/api/capability/TesseractBaseCapability.java new file mode 100644 index 00000000..abd43e5c --- /dev/null +++ b/src/main/java/tesseract/api/capability/TesseractBaseCapability.java @@ -0,0 +1,24 @@ +package tesseract.api.capability; + +import java.util.ArrayDeque; +import java.util.Deque; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import tesseract.api.IConnectable; + +public abstract class TesseractBaseCapability { + public final T tile; + public final Direction side; + public final boolean isNode; + public final ITransactionModifier callback; + protected boolean isSending; + + public TesseractBaseCapability(T tile, Direction side, boolean isNode, ITransactionModifier callback) { + this.tile = tile; + this.side = side; + this.isNode = isNode; + this.callback = callback; + this.isSending = false; + } +} diff --git a/src/main/java/tesseract/api/capability/TesseractFluidCapability.java b/src/main/java/tesseract/api/capability/TesseractFluidCapability.java new file mode 100644 index 00000000..16f0040e --- /dev/null +++ b/src/main/java/tesseract/api/capability/TesseractFluidCapability.java @@ -0,0 +1,117 @@ +package tesseract.api.capability; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler; +import tesseract.Tesseract; +import tesseract.api.fluid.*; +import tesseract.graph.Graph; +import tesseract.graph.Path; +import tesseract.util.Pos; + +import javax.annotation.Nonnull; + + +public class TesseractFluidCapability extends TesseractBaseCapability implements IFluidNode { + + private FluidTransaction old; + + public TesseractFluidCapability(T tile, Direction dir, boolean isNode, ITransactionModifier callback) { + super(tile, dir, isNode, callback); + } + + @Override + public int getTanks() { + return 1; + } + + @Nonnull + @Override + public FluidStack getFluidInTank(int tank) { + return FluidStack.EMPTY; + } + + @Override + public int getTankCapacity(int tank) { + return Integer.MAX_VALUE; + } + + @Override + public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { + return true; + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + if (this.isSending) return 0; + this.isSending = true; + if (action.execute()) { + if (old == null) { + this.isSending = false; + return 0; + } + old.commit(); + } else { + long pos = tile.getBlockPos().asLong(); + FluidTransaction transaction = new FluidTransaction(resource.copy(), a -> { + }); + if (!this.isNode) { + Tesseract.FLUID.getController(tile.getLevel(), pos).insert(pos, side, transaction, callback); + } else { + for (Direction dir : Graph.DIRECTIONS) { + if (dir == side || !this.tile.connects(dir)) continue; + Tesseract.FLUID.getController(tile.getLevel(), pos).insert(Pos.offset(pos, dir), dir.getOpposite(), transaction, callback); + } + } + this.old = transaction; + } + this.isSending = false; + return resource.getAmount() - this.old.stack.getAmount(); + } + + @Nonnull + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + return FluidStack.EMPTY; + } + + @Nonnull + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + return FluidStack.EMPTY; + } + + @Override + public int getPriority(Direction direction) { + return 0; + } + + @Override + public boolean canOutput() { + return true; + } + + @Override + public boolean canInput() { + return true; + } + + @Override + public boolean canInput(Direction direction) { + return true; + } + + @Override + public boolean canOutput(Direction direction) { + return true; + } + + @Override + public boolean canInput(FluidStack fluid, Direction direction) { + return true; + } +} diff --git a/src/main/java/tesseract/api/capability/TesseractGTCapability.java b/src/main/java/tesseract/api/capability/TesseractGTCapability.java new file mode 100644 index 00000000..e9ddc133 --- /dev/null +++ b/src/main/java/tesseract/api/capability/TesseractGTCapability.java @@ -0,0 +1,248 @@ +package tesseract.api.capability; + +import javax.annotation.Nullable; + +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.INBT; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.CapabilityInject; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.util.LazyOptional; +import tesseract.Tesseract; +import tesseract.api.gt.*; +import tesseract.graph.Graph; +import tesseract.graph.Path; +import tesseract.util.Pos; + +public class TesseractGTCapability extends TesseractBaseCapability implements IEnergyHandler { + @CapabilityInject(IEnergyHandler.class) + public static final Capability ENERGY_HANDLER_CAPABILITY; + + static { + ENERGY_HANDLER_CAPABILITY = null; + } + + public static void register() { + CapabilityManager.INSTANCE.register(IEnergyHandler.class, new Capability.IStorage() { + @Nullable + @Override + public INBT writeNBT(Capability capability, IEnergyHandler instance, Direction side) { + return null; + } + + @Override + public void readNBT(Capability capability, IEnergyHandler instance, Direction side, INBT nbt) { + + } + }, () -> new IEnergyHandler() { + @Override + public GTConsumer.State getState() { + return null; + } + + @Override + public CompoundNBT serializeNBT() { + return new CompoundNBT(); + } + + @Override + public void deserializeNBT(CompoundNBT nbt) { + + } + + @Override + public boolean insert(GTTransaction transaction) { + return false; + } + + @Override + public boolean extractEnergy(GTTransaction.TransferData data) { + return false; + } + + @Override + public boolean addEnergy(GTTransaction.TransferData data) { + return false; + } + + @Override + public GTTransaction extract(GTTransaction.Mode mode) { + return new GTTransaction(0, 0, a -> { + }); + } + + @Override + public long getEnergy() { + return 0; + } + + @Override + public long getCapacity() { + return 0; + } + + @Override + public long getOutputAmperage() { + return 0; + } + + @Override + public long getOutputVoltage() { + return 0; + } + + @Override + public long getInputAmperage() { + return 0; + } + + @Override + public long getInputVoltage() { + return 0; + } + + @Override + public boolean canOutput() { + return false; + } + + @Override + public boolean canInput() { + return false; + } + + @Override + public boolean canInput(Direction direction) { + return false; + } + + @Override + public boolean canOutput(Direction direction) { + return false; + } + }); + } + + private final IGTCable cable; + + public TesseractGTCapability(T tile, Direction dir, boolean isNode, ITransactionModifier modifier) { + super(tile, dir, isNode, modifier); + this.cable = tile; + } + + @Override + public boolean insert(GTTransaction transaction) { + boolean flag = false; + if (this.isSending) return false; + this.isSending = true; + long pos = tile.getBlockPos().asLong(); + if (!this.isNode) { + long old = transaction.getAvailableAmps(); + Tesseract.GT_ENERGY.getController(tile.getLevel(), pos).insert(pos, side, transaction, callback); + flag = transaction.getAvailableAmps() < old; + } else { + for (Direction dir : Graph.DIRECTIONS) { + if (dir == side || !this.tile.connects(dir)) continue; + Tesseract.GT_ENERGY.getController(tile.getLevel(), pos).insert(Pos.offset(pos, dir), dir.getOpposite(), transaction, callback); + } + } + this.isSending = false; + return flag; + } + + @Override + public boolean extractEnergy(GTTransaction.TransferData data) { + return false; + } + + @Override + public boolean addEnergy(GTTransaction.TransferData data) { + return false; + } + + @Override + public GTTransaction extract(GTTransaction.Mode mode) { + return new GTTransaction(0, 0, a -> { + }); + } + + @Override + public long getEnergy() { + return 0; + } + + @Override + public long getCapacity() { + return 0; + } + + + + @Override + public long availableAmpsInput() { + return Long.MAX_VALUE; + } + + @Override + public long availableAmpsOutput() { + return Long.MAX_VALUE; + } + + @Override + public long getOutputAmperage() { + return Long.MAX_VALUE; + } + + @Override + public long getOutputVoltage() { + return cable.getVoltage(); + } + + @Override + public long getInputAmperage() { + return Long.MAX_VALUE; + } + + @Override + public long getInputVoltage() { + return cable.getVoltage(); + } + + @Override + public boolean canOutput() { + return true; + } + + @Override + public boolean canInput() { + return true; + } + + @Override + public boolean canInput(Direction direction) { + return true; + } + + @Override + public boolean canOutput(Direction direction) { + return true; + } + + @Override + public GTConsumer.State getState() { + return new GTConsumer.State(this); + } + + @Override + public CompoundNBT serializeNBT() { + return null; + } + + @Override + public void deserializeNBT(CompoundNBT nbt) { + + } +} diff --git a/src/main/java/tesseract/api/capability/TesseractItemCapability.java b/src/main/java/tesseract/api/capability/TesseractItemCapability.java new file mode 100644 index 00000000..d99bbc5e --- /dev/null +++ b/src/main/java/tesseract/api/capability/TesseractItemCapability.java @@ -0,0 +1,108 @@ +package tesseract.api.capability; + +import javax.annotation.Nonnull; + +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import tesseract.Tesseract; +import tesseract.api.item.IItemNode; +import tesseract.api.item.IItemPipe; +import tesseract.api.item.ItemTransaction; +import tesseract.graph.Graph; +import tesseract.util.Pos; + + +public class TesseractItemCapability extends TesseractBaseCapability implements IItemNode { + + private ItemTransaction old; + + public TesseractItemCapability(T tile, Direction dir, boolean isNode, ITransactionModifier onTransaction) { + super(tile, dir, isNode, onTransaction); + } + + @Override + public int getSlots() { + return 1; + } + + @Nonnull + @Override + public ItemStack getStackInSlot(int slot) { + return ItemStack.EMPTY; + } + + @Nonnull + @Override + public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) { + if (!simulate) { + old.commit(); + } else { + if (this.isSending) return stack; + this.isSending = true; + ItemTransaction transaction = new ItemTransaction(stack, a -> {}); + long pos = tile.getBlockPos().asLong(); + if (!isNode) { + Tesseract.ITEM.getController(tile.getLevel(), pos).insert(pos, this.side, transaction, callback); + } else { + for (Direction dir : Graph.DIRECTIONS) { + if (dir == this.side || !this.tile.connects(dir)) continue; + Tesseract.ITEM.getController(tile.getLevel(), pos).insert(Pos.offset(pos, dir), dir.getOpposite(), transaction, callback); + } + } + this.old = transaction; + } + this.isSending = false; + return old.stack.copy(); + } + + @Nonnull + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return ItemStack.EMPTY; + } + + @Override + public int getSlotLimit(int slot) { + return 1; + } + + @Override + public boolean isItemValid(int slot, @Nonnull ItemStack stack) { + return true; + } + + @Override + public int getPriority(Direction direction) { + return 0; + } + + @Override + public boolean isEmpty(int slot) { + return false; + } + + @Override + public boolean canOutput() { + return true; + } + + @Override + public boolean canInput() { + return true; + } + + @Override + public boolean canInput(Direction direction) { + return true; + } + + @Override + public boolean canOutput(Direction direction) { + return true; + } +} diff --git a/src/main/java/tesseract/api/fe/FEConsumer.java b/src/main/java/tesseract/api/fe/FEConsumer.java index 293f496f..964f457e 100644 --- a/src/main/java/tesseract/api/fe/FEConsumer.java +++ b/src/main/java/tesseract/api/fe/FEConsumer.java @@ -22,10 +22,10 @@ public class FEConsumer extends Consumer { * Creates instance of the consumer. * * @param consumer The consumer node. - * @param path The path information. + * @param path The path information. */ protected FEConsumer(IFENode consumer, Path path) { - super(consumer, path); + super(consumer, null, path); init(); } @@ -33,7 +33,7 @@ protected FEConsumer(IFENode consumer, Path path) { * Adds energy to the node. Returns quantity of energy that was accepted. * * @param maxReceive Amount of energy to be inserted. - * @param simulate If true, the insertion will only be simulated. + * @param simulate If true, the insertion will only be simulated. * @return Amount of energy that was (or would have been, if simulated) accepted by the storage. */ public long insert(long maxReceive, boolean simulate) { diff --git a/src/main/java/tesseract/api/fe/FEController.java b/src/main/java/tesseract/api/fe/FEController.java index ad59c81b..9c020ecc 100644 --- a/src/main/java/tesseract/api/fe/FEController.java +++ b/src/main/java/tesseract/api/fe/FEController.java @@ -6,22 +6,22 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.util.Direction; +import net.minecraft.world.World; import tesseract.api.Controller; import tesseract.api.ITickingController; -import tesseract.graph.Cache; -import tesseract.graph.Grid; -import tesseract.graph.INode; -import tesseract.graph.Path; -import tesseract.util.Dir; +import tesseract.api.capability.ITransactionModifier; +import tesseract.graph.*; import tesseract.util.Node; import tesseract.util.Pos; +import javax.annotation.Nonnull; import java.util.List; /** * Class acts as a controller in the group of a energy components. */ -public class FEController extends Controller { +public class FEController extends Controller { private long totalEnergy, lastEnergy; private final Long2LongMap holders = new Long2LongOpenHashMap(); @@ -29,11 +29,11 @@ public class FEController extends Controller { /** * Creates instance of the controller. - - * @param dim The dimension id. + * + * @param world The world. */ - public FEController(int dim) { - super(dim); + public FEController(World world) { + super(world, null); holders.defaultReturnValue(-1L); } @@ -45,19 +45,20 @@ public FEController(int dim) { * Finally, it will pre-build consumer objects which are available for the producers. So each producer has a list of possible * consumers with unique information about paths, loss, ect. *

+ * * @see tesseract.graph.Grid (Cache) */ @Override public void change() { data.clear(); - for (Long2ObjectMap.Entry> e : group.getNodes().long2ObjectEntrySet()) { + for (Long2ObjectMap.Entry> e : group.getNodes().long2ObjectEntrySet()) { long pos = e.getLongKey(); - IFENode producer = e.getValue().value(); + IFENode producer = null;//e.getValue().value(); if (producer.canOutput()) { Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { + for (Direction direction : Graph.DIRECTIONS) { if (producer.canOutput(direction)) { List consumers = new ObjectArrayList<>(); long side = position.offset(direction).asLong(); @@ -97,7 +98,7 @@ public void change() { /** * Merge the existing consumers with new ones. * - * @param producer The producer node. + * @param producer The producer node. * @param consumers The consumer nodes. */ private void onMerge(IFENode producer, List consumers) { @@ -117,11 +118,11 @@ private void onMerge(IFENode producer, List consumers) { * Adds available consumers to the list. * * @param consumers The consumer nodes. - * @param path The paths to consumers. - * @param pos The position of the producer. + * @param path The paths to consumers. + * @param pos The position of the producer. */ private void onCheck(List consumers, Path path, long pos) { - IFENode node = group.getNodes().get(pos).value(); + IFENode node = null;//group.getNodes().get(pos).value(); if (node.canInput()) consumers.add(new FEConsumer(node, path)); } @@ -213,6 +214,11 @@ public void tick() { } } + // @Override + // public int insert(long producerPos, long direction, Integer stack, boolean simulate) { + // return 0; + // } + @Override protected void onFrame() { lastEnergy = totalEnergy; @@ -220,14 +226,24 @@ protected void onFrame() { } @Override - public String[] getInfo() { - return new String[]{ - "Total Energy: ".concat(Long.toString(lastEnergy)) - }; + public void getInfo(long pos, @Nonnull List list) { + this.group.getGroupInfo(pos, list); + list.add(String.format("FE Data size: %d", this.data.size())); } + /*@Override + public void insert(long producerPos, long pipePos, Integer transaction) { + + }*/ + @Override public ITickingController clone(INode group) { return new FEController(dim).set(group); } + + @Override + public void insert(long producerPos, Direction side, Integer transaction, ITransactionModifier modifier) { + // TODO Auto-generated method stub + + } } \ No newline at end of file diff --git a/src/main/java/tesseract/api/fe/IFECable.java b/src/main/java/tesseract/api/fe/IFECable.java index 6d8f1f61..0f6ab714 100644 --- a/src/main/java/tesseract/api/fe/IFECable.java +++ b/src/main/java/tesseract/api/fe/IFECable.java @@ -9,6 +9,7 @@ public interface IFECable extends IConnectable { /** * Returns the maximum amount of energy that this item component will permit to pass through or be received in a single tick. + * * @return A positive integer representing the maximum packets, zero or negative indicates that this component accepts no energy. */ long getCapacity(); diff --git a/src/main/java/tesseract/api/fe/IFENode.java b/src/main/java/tesseract/api/fe/IFENode.java index 05edfbd0..50f4b2af 100644 --- a/src/main/java/tesseract/api/fe/IFENode.java +++ b/src/main/java/tesseract/api/fe/IFENode.java @@ -1,7 +1,7 @@ package tesseract.api.fe; +import net.minecraft.util.Direction; import tesseract.api.IConnectable; -import tesseract.util.Dir; /** * A flux node is the unit of interaction with flux inventories. @@ -14,16 +14,18 @@ public interface IFENode extends IConnectable { /** * Adds energy to the node. Returns quantity of energy that was accepted. + * * @param maxReceive Maximum amount of energy to be inserted. - * @param simulate If true, the insertion will only be simulated. + * @param simulate If true, the insertion will only be simulated. * @return Amount of energy that was (or would have been, if simulated) accepted by the storage. */ long insert(long maxReceive, boolean simulate); /** * Removes energy from the node. Returns quantity of energy that was removed. + * * @param maxExtract Maximum amount of energy to be extracted. - * @param simulate If true, the extraction will only be simulated. + * @param simulate If true, the extraction will only be simulated. * @return Amount of energy that was (or would have been, if simulated) extracted from the storage. */ long extract(long maxExtract, boolean simulate); @@ -45,20 +47,23 @@ public interface IFENode extends IConnectable { /** * Gets if this storage can have energy extracted. + * * @return If this is false, then any calls to extractEnergy will return 0. */ boolean canOutput(); /** * Used to determine if this storage can receive energy. + * * @return If this is false, then any calls to receiveEnergy will return 0. */ boolean canInput(); /** * Used to determine which sides can output energy (if any). + * * @param direction Direction to the output. * @return Returns true if the given direction is output side. */ - boolean canOutput(Dir direction); + boolean canOutput(Direction direction); } diff --git a/src/main/java/tesseract/api/fluid/FluidConsumer.java b/src/main/java/tesseract/api/fluid/FluidConsumer.java index eb8cd12e..c67e5875 100644 --- a/src/main/java/tesseract/api/fluid/FluidConsumer.java +++ b/src/main/java/tesseract/api/fluid/FluidConsumer.java @@ -1,29 +1,41 @@ package tesseract.api.fluid; + +import net.minecraft.util.Direction; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.IFluidHandler; +import tesseract.api.ConnectionType; import tesseract.api.Consumer; import tesseract.graph.Path; -import tesseract.util.Dir; + /** * A class that acts as a container for a fluid consumer. */ -public class FluidConsumer extends Consumer> { +public class FluidConsumer extends Consumer { private int isProof = 1; private int minCapacity = Integer.MAX_VALUE; + + public int getMinPressure() { + return minPressure; + } + private int minPressure = Integer.MAX_VALUE; private int minTemperature = Integer.MAX_VALUE; - private final Dir input; + public final Direction input; + + public long lowestPipePosition = -1; /** * Creates instance of the consumer. * * @param consumer The consumer node. - * @param path The path information. - * @param dir The added direction. + * @param path The path information. + * @param dir The added direction. */ - protected FluidConsumer(IFluidNode consumer, Path path, Dir dir) { - super(consumer, path); + public FluidConsumer(IFluidNode consumer,IFluidNode producer, Path path, Direction dir) { + super(consumer,producer, path); init(); this.input = dir; } @@ -31,19 +43,19 @@ protected FluidConsumer(IFluidNode consumer, Path path, Dir dir) /** * Adds fluid to the node. Returns amount of fluid that was filled. * - * @param data FluidData attempting to fill the tank. + * @param data FluidData attempting to fill the tank. * @param simulate If true, the fill will only be simulated. * @return Amount of fluid that was accepted (or would be, if simulated) by the tank. */ - public int insert(FluidData data, boolean simulate) { - return node.insert(data, simulate); + public int insert(FluidStack data, boolean simulate) { + return node.fill(data, simulate ? IFluidHandler.FluidAction.SIMULATE : IFluidHandler.FluidAction.EXECUTE); } /** * @param fluid The Fluid to be queried. * @return If the tank can hold the fluid (EVER, not at the time of query). */ - public boolean canHold(Object fluid) { + public boolean canHold(FluidStack fluid) { return node.canInput(fluid, input); } @@ -56,12 +68,11 @@ public int getPriority() { /** * @param temperature The current temperature. - * @param pressure The current pressure. - * @param proof True if current liquid is in a gas state. + * @param proof True if current liquid is in a gas state. * @return Checks that the consumer is able to receive fluid. */ - public boolean canHandle(int temperature, int pressure, boolean proof) { - return minTemperature >= temperature && minPressure >= pressure && isProof == (proof ? 1 : 0); + public boolean canHandle(int temperature, boolean proof) { + return minTemperature >= temperature /*&& minPressure >= pressure */ && isProof == (proof ? 1 : 0); } @Override @@ -69,6 +80,9 @@ protected void onConnectorCatch(IFluidPipe pipe) { isProof = Math.min(isProof, pipe.isGasProof() ? 1 : 0); minTemperature = Math.min(minTemperature, pipe.getTemperature()); minCapacity = Math.min(minCapacity, pipe.getCapacity()); + if (pipe.getPressure() < minPressure && connection == ConnectionType.SINGLE) { + lowestPipePosition = this.getFull().long2ObjectEntrySet().stream().filter(t -> t.getValue() == pipe).findFirst().get().getLongKey(); + } minPressure = Math.min(minPressure, pipe.getPressure()); } } diff --git a/src/main/java/tesseract/api/fluid/FluidController.java b/src/main/java/tesseract/api/fluid/FluidController.java index a07e3e0e..7ffb53a0 100644 --- a/src/main/java/tesseract/api/fluid/FluidController.java +++ b/src/main/java/tesseract/api/fluid/FluidController.java @@ -1,20 +1,22 @@ package tesseract.api.fluid; -import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.longs.*; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.util.Direction; +import net.minecraft.util.Tuple; +import net.minecraft.world.World; +import net.minecraftforge.fluids.FluidStack; import tesseract.api.ConnectionType; import tesseract.api.Consumer; import tesseract.api.Controller; import tesseract.api.ITickingController; +import tesseract.api.capability.ITransactionModifier; +import tesseract.api.capability.TesseractBaseCapability; import tesseract.graph.*; -import tesseract.util.Dir; import tesseract.util.Node; import tesseract.util.Pos; -import java.util.Comparator; +import javax.annotation.Nonnull; import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -22,63 +24,74 @@ /** * Class acts as a controller in the group of a fluid components. */ -public class FluidController> extends Controller implements IFluidEvent { +public class FluidController extends Controller + implements IFluidEvent { + public final static boolean HARDCORE_PIPES = false; + public final static boolean SLOOSH = false; + public static double PIPE_LEAK = 0.8; private long totalPressure, lastPressure; - private int maxTemperature, isLeaking, lastTemperature, lastLeaking; - private final Long2ObjectMap> holders = new Long2ObjectLinkedOpenHashMap<>(); - private final Object2ObjectMap>>> data = new Object2ObjectLinkedOpenHashMap<>(); + private int maxTemperature, lastTemperature; + private boolean isLeaking, lastLeaking; + private final Long2ObjectMap>> data = new Long2ObjectLinkedOpenHashMap<>(); + private final Long2IntMap pressureData = new Long2IntOpenHashMap(10); + //public final Long2IntMap sentPressure = new Long2IntOpenHashMap(10); /** * Creates instance of the controller. * - * @param dim The dimension id. + * @param world the world. */ - public FluidController(int dim) { - super(dim); + public FluidController(World world, Graph.INodeGetter getter) { + super(world, getter); } - @Override - public void change() { - data.clear(); - - for (Long2ObjectMap.Entry> e : group.getNodes().long2ObjectEntrySet()) { - long pos = e.getLongKey(); - N producer = e.getValue().value(); - - if (producer.canOutput()) { - Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { - if (producer.canOutput(direction)) { - List> consumers = new ObjectArrayList<>(); - long side = position.offset(direction).asLong(); - - if (group.getNodes().containsKey(side)) { - onCheck(consumers, null, direction.getOpposite(), side); - } else { - Grid grid = group.getGridAt(side, direction); - if (grid != null) { - for (Path path : grid.getPaths(pos)) { - if (!path.isEmpty()) { - Node target = path.target(); - assert target != null; - onCheck(consumers, path, target.getDirection(), target.asLong()); - } - } - } - } - - if (!consumers.isEmpty()) { - data.computeIfAbsent(producer, map -> new EnumMap<>(Dir.class)).put(direction, consumers); + private void handleInput(long pos, NodeCache producers) { + if (data.containsKey(pos)) + return; + for (Map.Entry tup : producers.values()) { + IFluidNode producer = tup.getValue(); + Direction direction = tup.getKey(); + if (producer.canOutput(direction)) { + List consumers = new ObjectArrayList<>(); + long side = Pos.offset(pos, direction); + + Grid grid = group.getGridAt(side, direction); + if (grid != null) { + for (Path path : grid.getPaths(pos)) { + if (!path.isEmpty()) { + Node target = path.target(); + assert target != null; + onCheck(producer, consumers, path, target.getDirection(), target.asLong()); } } + } else if (group.getNodes().containsKey(side)) { + onCheck(producer, consumers, null, direction.getOpposite(), side); + } + + if (!consumers.isEmpty()) { + data.computeIfAbsent(pos, map -> new EnumMap<>(Direction.class)) + .put(direction.getOpposite(), consumers); } } } + } - for (Map>> map : data.values()) { - for (List> consumers : map.values()) { - consumers.sort(Consumer.COMPARATOR); + @Override + public void change() { + if (!SLOOSH) { + data.clear(); + this.group.connectors().forEach(t -> t.value().getHolder().clear()); + for (Long2ObjectMap.Entry> e : group.getNodes().long2ObjectEntrySet()) { + handleInput(e.getLongKey(), e.getValue()); + } + + this.group.pipeNodes().forEach(t -> handleInput(t, new NodeCache<>(t, getter))); + + for (Map> map : data.values()) { + for (List consumers : map.values()) { + consumers.sort(Consumer.COMPARATOR); + } } } } @@ -87,114 +100,152 @@ public void change() { * Adds available consumers to the list. * * @param consumers The consumer nodes. - * @param path The paths to consumers. - * @param dir The added direction. - * @param pos The position of the producer. + * @param path The paths to consumers. + * @param dir The added direction. + * @param pos The position of the producer. */ - private void onCheck(List> consumers, Path path, Dir dir, long pos) { - N node = group.getNodes().get(pos).value(); - if (node.canInput()) consumers.add(new FluidConsumer<>(node, path, dir)); + private void onCheck(IFluidNode producer, List consumers, Path path, Direction dir, long pos) { + IFluidNode node = group.getNodes().get(pos).value(dir); + if (node != null && node.canInput()) + consumers.add(new FluidConsumer(node,producer, path, dir)); } @Override - public void tick() { - super.tick(); - holders.clear(); - - for (Object2ObjectMap.Entry>>> e : data.object2ObjectEntrySet()) { - N producer = e.getKey(); - - for (Map.Entry>> c : e.getValue().entrySet()) { - Dir direction = c.getKey(); - - int tank = producer.getAvailableTank(direction); - if (tank == -1) { - continue; - } - - int outputAmount = producer.getOutputAmount(direction); - - for (FluidConsumer consumer : c.getValue()) { - - FluidData data = producer.extract(tank, outputAmount, true); - if (data == null) { - continue; - } - - T stack = data.getStack(); - if (!consumer.canHold(stack)) { - continue; - } + public void insert(long producerPos, Direction side, FluidTransaction transaction, ITransactionModifier modifier) { + if (SLOOSH) + return; + if (!transaction.isValid()) + return; + Map> map = this.data.get(Pos.offset(producerPos, side)); + if (map == null) + return; + List list = map.get(side); + if (list == null) + return; + + pressureData.clear(); + + loop: for (FluidConsumer consumer : list) { + FluidStack data = transaction.stack.copy(); + if (!consumer.canHold(data)) { + continue; + } - int amount = consumer.insert(data, true); - if (amount <= 0) { - continue; + int amount = consumer.insert(data, true); + if (amount <= 0) { + continue; + } + if (!HARDCORE_PIPES) { + if (consumer.getConnection() == ConnectionType.SINGLE) { + if (consumer.lowestPipePosition == -1) { + amount = Math.min(amount, consumer.getMinPressure() * 20); + } else { + amount = Math.min(amount, this.group.getConnector(consumer.lowestPipePosition).value().getHolder().getPressureAvailable()); } - - int temperature = data.getTemperature(); - boolean isGaseous = data.isGaseous(); - - FluidData drained = producer.extract(tank, amount, false); - - assert drained != null; - - // If we are here, then path had some invalid pipes which not suits the limits of temp/pressure/gas - if (consumer.getConnection() != ConnectionType.ADJACENT && !consumer.canHandle(temperature, amount, isGaseous)) { - // Find corrupt pipe and return - for (Long2ObjectMap.Entry p : consumer.getFull().long2ObjectEntrySet()) { - long pos = p.getLongKey(); - IFluidPipe pipe = p.getValue(); - - switch (pipe.getHandler(temperature, amount, isGaseous)) { - case FAIL_TEMP: - onPipeOverTemp(dim, pos, temperature); - return; - case FAIL_PRESSURE: - onPipeOverPressure(dim, pos, amount); - return; - case FAIL_LEAK: - onPipeGasLeak(dim, pos, drained); - break; - } + } else { + for (Long2ObjectMap.Entry entry : consumer.getCross() + .long2ObjectEntrySet()) { + FluidHolder holder = entry.getValue().getHolder(); + if (!holder.allowFluid(data.getFluid())) { + amount = 0; + break; } + int tempData = pressureData.get(entry.getLongKey()); + amount = Math.min(amount, holder.getPressureAvailable() - tempData); + if (amount == 0) + continue loop; } - - // Stores the pressure into holder for path only for variate connection - if (consumer.getConnection() == ConnectionType.VARIATE) { - for (Long2ObjectMap.Entry p : consumer.getCross().long2ObjectEntrySet()) { - long pos = p.getLongKey(); - IFluidPipe pipe = p.getValue(); - - holders.computeIfAbsent(pos, h -> new FluidHolder<>(pipe)).add(amount, stack); - } + } + } + data.setAmount(amount); + if (data.isEmpty()) + continue; + + if (consumer.getConnection() == ConnectionType.VARIATE) { + for (Long2ObjectMap.Entry p : consumer.getCross().long2ObjectEntrySet()) { + final int finalAmount = amount; + pressureData.compute(p.getLongKey(), (k, v) -> v == null ? finalAmount : v + finalAmount); + if (p.getValue() instanceof TesseractBaseCapability) { + TesseractBaseCapability cap = (TesseractBaseCapability) p.getValue(); + cap.callback.modify(data,cap.side, side, true); } + } + } + transaction.addData(data.copy(), a -> dataCommit(consumer, a)); - maxTemperature = Math.max(temperature, maxTemperature); - isLeaking = Math.max(isGaseous ? 1: 0, isLeaking); - totalPressure += amount; - - consumer.insert(drained, false); - - outputAmount -= amount; - if (outputAmount <= 0) { + if (transaction.stack.isEmpty()) + break; + } + } + public void dataCommit(FluidConsumer consumer, FluidStack stack) { + int temperature = stack.getFluid().getAttributes().getTemperature(); + int amount = stack.getAmount(); + boolean isGaseous = stack.getFluid().getAttributes().isGaseous(); + boolean cantHandle = !consumer.canHandle(temperature, isGaseous); + if (!cantHandle) { + for (Long2ObjectMap.Entry p : consumer.getFull().long2ObjectEntrySet()) { + long pos = p.getLongKey(); + IFluidPipe pipe = p.getValue(); + switch (pipe.getHandler(stack, temperature, isGaseous)) { + case FAIL_TEMP: + onPipeOverTemp(getWorld(), pos, temperature); + return; + case FAIL_LEAK: + stack = onPipeGasLeak(getWorld(), pos, stack); + isLeaking = true; + break; + case FAIL_CAPACITY: + break; + default: break; - } } } } - - for (Long2ObjectMap.Entry> e : holders.long2ObjectEntrySet()) { - long pos = e.getLongKey(); - FluidHolder absorber = e.getValue(); - - // TODO: Find proper path to destroy - - if (absorber.isOverPressure()) { - onPipeOverPressure(dim, pos, absorber.getPressure()); + if (consumer.getConnection() == ConnectionType.SINGLE) { + FluidHolder holder = this.group.getConnector(consumer.lowestPipePosition).value().getHolder(); + holder.use(stack.getAmount(), stack.getFluid(), getWorld().getGameTime()); + if (holder.isOverPressure()) { + onPipeOverPressure(getWorld(), consumer.lowestPipePosition, amount, stack); + return; } - if (absorber.isOverCapacity()) { - onPipeOverCapacity(dim, pos, absorber.getCapacity()); + if (holder.isOverCapacity()) { + onPipeOverCapacity(getWorld(), consumer.lowestPipePosition, amount, stack); + return; } + } else if (consumer.getConnection() == ConnectionType.VARIATE) { + for (Long2ObjectMap.Entry pathHolderEntry : consumer.getCross() + .long2ObjectEntrySet()) { + FluidHolder holder = pathHolderEntry.getValue().getHolder(); + holder.use(stack.getAmount(), stack.getFluid(), getWorld().getGameTime()); + if (holder.isOverPressure()) { + onPipeOverPressure(getWorld(), pathHolderEntry.getLongKey(), amount, stack); + return; + } + if (holder.isOverCapacity()) { + onPipeOverCapacity(getWorld(), pathHolderEntry.getLongKey(), amount, stack); + return; + } + if (pathHolderEntry.getValue() instanceof TesseractBaseCapability) { + TesseractBaseCapability cap = (TesseractBaseCapability) pathHolderEntry.getValue(); + cap.callback.modify(data,cap.side, consumer.input, false); + } + } + } + /*consumer.getFull().keySet().forEach(l -> { + int pressure = amount + this.sentPressure.get(l); + this.sentPressure.put(l, pressure); + });*/ + maxTemperature = Math.max(temperature, maxTemperature); + totalPressure += amount; + consumer.insert(stack, false); + } + + @Override + public void tick() { + super.tick(); + // sentPressure.clear(); + for (Cache pipe : this.group.connectors()) { + pipe.value().getHolder().tick(getWorld().getGameTime()); } } @@ -204,20 +255,21 @@ protected void onFrame() { lastPressure = totalPressure; lastLeaking = isLeaking; totalPressure = 0L; - maxTemperature = isLeaking = 0; + maxTemperature = 0; + isLeaking = false; } @Override - public String[] getInfo() { - return new String[]{ - "Maximum Temperature: ".concat(Integer.toString(lastTemperature)), - "Total Pressure: ".concat(Long.toString(lastPressure)), - "Any Leaks: ".concat(lastLeaking == 1 ? "Yes" : "No") - }; + public void getInfo(long pos, @Nonnull List list) { + if (this.group != null) { + this.group.getGroupInfo(pos, list); + list.add(String.format("Fluid Data size: %d", this.data.size())); + } } @Override public ITickingController clone(INode group) { - return new FluidController<>(dim).set(group); + return new FluidController(dim, getter).set(group); } + } diff --git a/src/main/java/tesseract/api/fluid/FluidData.java b/src/main/java/tesseract/api/fluid/FluidData.java deleted file mode 100644 index 62bb64f1..00000000 --- a/src/main/java/tesseract/api/fluid/FluidData.java +++ /dev/null @@ -1,54 +0,0 @@ -package tesseract.api.fluid; - -/** - * A class that acts as a wrapper for a item stack and a slot index. - */ -public class FluidData { - - private final T stack; - private final boolean isGaseous; - private final int amount, temperature; - - /** - * Creates instance of the data. - * - * @param stack The FluidStack object. - * @param amount The amount of the fluid. - * @param temperature The temperature of the fluid. - * @param isGaseous The state of the fluid. - */ - public FluidData(T stack, int amount, int temperature, boolean isGaseous) { - this.stack = stack; - this.amount = amount; - this.temperature = temperature; - this.isGaseous = isGaseous; - } - - /** - * @return Gets the FluidStack object. - */ - public T getStack() { - return stack; - } - - /** - * @return Gets the fluid amount inside a FluidStack. - */ - public int getAmount() { - return amount; - } - - /** - * @return Gets the temperature. - */ - public int getTemperature() { - return temperature; - } - - /** - * @return Checks the gas state. - */ - public boolean isGaseous() { - return isGaseous; - } -} \ No newline at end of file diff --git a/src/main/java/tesseract/api/fluid/FluidHolder.java b/src/main/java/tesseract/api/fluid/FluidHolder.java index 11a767b0..c8e1b55c 100644 --- a/src/main/java/tesseract/api/fluid/FluidHolder.java +++ b/src/main/java/tesseract/api/fluid/FluidHolder.java @@ -2,44 +2,56 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.fluid.Fluid; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fluids.FluidStack; import java.util.Set; /** * A class that acts as holder of the fluid that has passed thought pipes. */ -public class FluidHolder { +public class FluidHolder { - private int pressure; - private final int maxPressure, maxCapacity; - private final Set fluids = new ObjectOpenHashSet<>(); + private int pressureAvailable; + public final int tickPressure, maxCapacity; + private final Set fluids = new ObjectOpenHashSet<>(); /** * Creates instance of the holder. * * @param pipe The pipe connector. */ - protected FluidHolder(IFluidPipe pipe) { + public FluidHolder(IFluidPipe pipe) { this.maxCapacity = pipe.getCapacity(); - this.maxPressure = pipe.getPressure(); + this.tickPressure = pipe.getPressure(); + + this.pressureAvailable = tickPressure * 20; + } + + public void tick(long time) { + pressureAvailable = Math.min(pressureAvailable + tickPressure, tickPressure * 20); + this.fluids.removeIf(t -> time - t.timeAdded >= 20); } /** - * Adds a new liquid. + * Uses up a part of this pipe and adds the fluid to the set. * * @param pressure The added pressure. - * @param fluid The fluid type. + * @param fluid The fluid type. */ - public void add(int pressure, T fluid) { - this.pressure += pressure; - fluids.add(fluid); + public void use(int pressure, Fluid fluid, long currentTime) { + this.pressureAvailable -= pressure; + SetHolder holder = new SetHolder(fluid, currentTime); + fluids.remove(holder); + fluids.add(holder); } /** - * @return Gets a current pressure. + * @return Gets the current available pressure. If 0 then no liquid can be sent */ - public int getPressure() { - return pressure; + public int getPressureAvailable() { + return pressureAvailable; } /** @@ -53,7 +65,7 @@ public int getCapacity() { * @return Checks that the holder is not able to handle pressure. */ public boolean isOverPressure() { - return maxPressure < pressure; + return pressureAvailable < 0; } /** @@ -62,4 +74,53 @@ public boolean isOverPressure() { public boolean isOverCapacity() { return maxCapacity < fluids.size(); } + + public boolean allowFluid(Fluid fluid) { + SetHolder holder = new SetHolder(fluid, 0); + if (fluids.contains(holder)) { + return true; + } + return maxCapacity > fluids.size(); + } + + public Set getFluids() { + return fluids; + } + + public void clear() { + this.fluids.clear(); + this.pressureAvailable = tickPressure*20; + } + + public static class SetHolder { + public final Fluid fluid; + public long timeAdded; + + public SetHolder(final Fluid fluid, long added) { + this.fluid = fluid; + this.timeAdded = added; + } + + @Override + public int hashCode() { + return fluid.getRegistryName().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SetHolder) { + return ((SetHolder) obj).fluid.getRegistryName().equals(this.fluid.getRegistryName()); + } + if (obj instanceof Fluid) { + return ((Fluid) obj).getRegistryName().equals(this.fluid.getRegistryName()); + } + if (obj instanceof FluidStack) { + return ((FluidStack) obj).getFluid().getRegistryName().equals(this.fluid.getRegistryName()); + } + if (obj instanceof ResourceLocation) { + return obj.equals(this.fluid.getRegistryName()); + } + return false; + } + } } diff --git a/src/main/java/tesseract/api/fluid/FluidTransaction.java b/src/main/java/tesseract/api/fluid/FluidTransaction.java new file mode 100644 index 00000000..6df3262b --- /dev/null +++ b/src/main/java/tesseract/api/fluid/FluidTransaction.java @@ -0,0 +1,32 @@ +package tesseract.api.fluid; + +import net.minecraftforge.fluids.FluidStack; +import tesseract.api.Transaction; + +import java.util.function.Consumer; + +public class FluidTransaction extends Transaction { + + public final FluidStack stack; + + public FluidTransaction(FluidStack stack, Consumer consumer) { + super(consumer); + this.stack = stack; + } + + public void addData(FluidStack stack, Consumer consumer) { + this.addData(stack); + this.stack.setAmount(this.stack.getAmount() - stack.getAmount()); + this.onCommit(consumer); + } + + @Override + public boolean isValid() { + return stack.getAmount() > 0; + } + + @Override + public boolean canContinue() { + return false; + } +} diff --git a/src/main/java/tesseract/api/fluid/IFluidEvent.java b/src/main/java/tesseract/api/fluid/IFluidEvent.java index 15904c62..3fd64b50 100644 --- a/src/main/java/tesseract/api/fluid/IFluidEvent.java +++ b/src/main/java/tesseract/api/fluid/IFluidEvent.java @@ -1,5 +1,8 @@ package tesseract.api.fluid; +import net.minecraft.world.World; +import net.minecraftforge.fluids.FluidStack; + /** * Interface for handling a fluid events. (Controller will handle them) */ @@ -7,41 +10,46 @@ public interface IFluidEvent { /** * Executes when the cable trying to transport higher amount of pressure than can. - * @param dim The dimension id. - * @param pos The pipe position. + * + * @param world The world. + * @param pos The pipe position. * @param pressure The current pressure. */ - default void onPipeOverPressure(int dim, long pos, int pressure) { + default void onPipeOverPressure(World world, long pos, int pressure, FluidStack fluid) { //NOOP } /** * Executes when the cable trying to transport higher amount of liquids than can. - * @param dim The dimension id. - * @param pos The pipe position. + * + * @param world The world. + * @param pos The pipe position. * @param capacity The current capacity. */ - default void onPipeOverCapacity(int dim, long pos, int capacity) { + default void onPipeOverCapacity(World world, long pos, int capacity, FluidStack fluid) { //NOOP } /** * Executes when the cable trying to transport higher amount of temperature than can. - * @param dim The dimension id. - * @param pos The pipe position. + * + * @param world The world. + * @param pos The pipe position. * @param temperature The current temperature. */ - default void onPipeOverTemp(int dim, long pos, int temperature) { + default void onPipeOverTemp(World world, long pos, int temperature) { //NOOP } /** - * Executes when the pipe trying to transport gas than can. - * @param dim The dimension id. - * @param pos The pipe position. + * Executes when the pipe trying to transport gas that can leak. + * Returns resulting fluid stack + * + * @param world The world. + * @param pos The pipe position. * @param fluid FluidData holding the Fluid to be queried. */ - default void onPipeGasLeak(int dim, long pos, FluidData fluid) { - //NOOP + default FluidStack onPipeGasLeak(World world, long pos, FluidStack fluid) { + return fluid; } } diff --git a/src/main/java/tesseract/api/fluid/IFluidNode.java b/src/main/java/tesseract/api/fluid/IFluidNode.java index 60c8e4b1..662c9a48 100644 --- a/src/main/java/tesseract/api/fluid/IFluidNode.java +++ b/src/main/java/tesseract/api/fluid/IFluidNode.java @@ -1,7 +1,19 @@ package tesseract.api.fluid; -import tesseract.api.IConnectable; -import tesseract.util.Dir; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler; + +import javax.annotation.Nonnull; + + +import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; +import tesseract.api.GraphWrapper; +import tesseract.graph.Graph; /** * An fluid node is the unit of interaction with fluid inventories. @@ -11,67 +23,152 @@ * DO NOT ASSUME that these objects are used internally in all cases. *

*/ -public interface IFluidNode extends IConnectable { - - /** - * Adds fluid to the node. Returns amount of fluid that was filled. - * @param data FluidData attempting to fill the tank. - * @param simulate If true, the fill will only be simulated. - * @return Amount of fluid that was accepted (or would be, if simulated) by the tank. - */ - int insert(FluidData data, boolean simulate); - - /** - * Removes fluid from the node. Returns amount of fluid that was drained. - * @param tank The tank to extract from. - * @param amount Maximum amount of fluid to be removed from the container. - * @param simulate If true, the drain will only be simulated. - * @return FluidData representing fluid that was removed (or would be, if simulated) from the tank. - */ - FluidData extract(int tank, int amount, boolean simulate); - - /** - * @param direction Direction to the proceed. - * @return Gets any available tank. (-1 when wasn't found any) - **/ - int getAvailableTank(Dir direction); - - /** - * @param direction Direction to the proceed. - * @return Gets the initial amount of pressure that can be output. - */ - int getOutputAmount(Dir direction); - +public interface IFluidNode extends IFluidHandler { /** * @param direction Direction to the proceed. * @return Returns the priority of this node as a number. */ - int getPriority(Dir direction); + int getPriority(Direction direction); /** * Gets if this storage can have fluid extracted. + * * @return If this is false, then any calls to extractEnergy will return 0. */ boolean canOutput(); /** * Used to determine if this storage can receive fluid. + * * @return If this is false, then any calls to receiveEnergy will return 0. */ boolean canInput(); + /** + * Used to determine if this storage can receive fluid. + * + * @return If this is false, then any calls to receiveEnergy will return 0. + */ + boolean canInput(Direction direction); + /** * Used to determine which sides can output fluid (if any). + * * @param direction Direction to the output. * @return Returns true if the given direction is output side. */ - boolean canOutput(Dir direction); + boolean canOutput(Direction direction); /** * Used to determine which fluids and at which direction can be consumed. - * @param fluid The Fluid to be queried. + * + * @param fluid The Fluid to be queried. * @param direction Direction to the input. * @return If the tank can input the fluid (EVER, not at the time of query). */ - boolean canInput(Object fluid, Dir direction); + boolean canInput(FluidStack fluid, Direction direction); + + /** + * Drains from the input tanks rather than output tanks. Useful for recipes. + * + * @param stack stack to drain. + * @param action execute/simulate + * @return the drained stack + */ + default FluidStack drainInput(FluidStack stack, IFluidHandler.FluidAction action) { + return drain(stack, action); + } + + class FluidTileWrapper implements IFluidNode { + private final TileEntity tile; + private final IFluidHandler handler; + + public FluidTileWrapper(TileEntity tile, IFluidHandler handler) { + this.tile = tile; + this.handler = handler; + } + + @Override + public int getPriority(Direction direction) { + return (!(handler instanceof IFluidNode) ? 0 : ((IFluidNode) handler).getPriority(direction)); + } + + @Override + public boolean canOutput() { + return (!(handler instanceof IFluidNode) || ((IFluidNode) handler).canOutput()); + } + + @Override + public boolean canInput() { + return (!(handler instanceof IFluidNode) || ((IFluidNode) handler).canInput()); + } + + @Override + public boolean canInput(Direction direction) { + return (!(handler instanceof IFluidNode) || ((IFluidNode) handler).canInput(direction)); + } + + @Override + public boolean canOutput(Direction direction) { + return (!(handler instanceof IFluidNode) || ((IFluidNode) handler).canOutput(direction)); + } + + @Override + public boolean canInput(FluidStack fluid, Direction direction) { + return (!(handler instanceof IFluidNode) || ((IFluidNode) handler).canInput(fluid, direction)); + } + + @Override + public int getTanks() { + return handler.getTanks(); + } + + @Nonnull + @Override + public FluidStack getFluidInTank(int tank) { + return handler.getFluidInTank(tank); + } + + @Override + public int getTankCapacity(int tank) { + return handler.getTankCapacity(tank); + } + + @Override + public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { + return handler.isFluidValid(tank, stack); + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + return handler.fill(resource, action); + } + + @Nonnull + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + return handler.drain(resource, action); + } + + @Nonnull + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + return handler.drain(maxDrain, action); + } + } + + GraphWrapper.ICapabilityGetter GETTER = ((level, pos, capSide, capCallback) -> { + TileEntity tile = level.getBlockEntity(BlockPos.of(pos)); + if (tile == null) { + return null; + } + LazyOptional capability = tile.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, capSide); + if (capability.isPresent()) { + if (capCallback != null) capability.addListener(o -> capCallback.run()); + IFluidHandler handler = capability.orElse(null); + return handler instanceof IFluidNode ? (IFluidNode) handler: new IFluidNode.FluidTileWrapper(tile, handler); + } else { + return null; + } + }); } diff --git a/src/main/java/tesseract/api/fluid/IFluidPipe.java b/src/main/java/tesseract/api/fluid/IFluidPipe.java index aa11d1f1..ab5e061b 100644 --- a/src/main/java/tesseract/api/fluid/IFluidPipe.java +++ b/src/main/java/tesseract/api/fluid/IFluidPipe.java @@ -1,5 +1,6 @@ package tesseract.api.fluid; +import net.minecraftforge.fluids.FluidStack; import tesseract.api.IConnectable; /** @@ -9,18 +10,21 @@ public interface IFluidPipe extends IConnectable { /** * Returns the maximum amount of packets that this fluid component will permit to pass through or be received in a single tick. + * * @return A positive integer representing the maximum packets, zero or negative indicates that this component accepts no fluid. */ int getCapacity(); /** * Returns the maximum amount of pressure that this fluid component will permit to pass through or be received in a single tick. + * * @return A positive integer representing the maximum amount, zero or negative indicates that this component accepts no fluid. */ int getPressure(); /** * Returns the maximum temperature that this fluid component will permit to pass through or be received in a single packet. + * * @return A positive integer representing the maximum accepted temp, zero or negative indicates that this component accepts no fluid. */ int getTemperature(); @@ -30,16 +34,19 @@ public interface IFluidPipe extends IConnectable { */ boolean isGasProof(); + FluidHolder getHolder(); + /** * @param temperature The current temperature. - * @param pressure The current pressure. - * @param proof True if current liquid is in a gas state. + * @param pressure The current pressure. + * @param proof True if current liquid is in a gas state. * @return Checks that the pipe is able to handle single packet. */ - default FluidStatus getHandler(int temperature, int pressure, boolean proof) { + default FluidStatus getHandler(FluidStack stack, int temperature, boolean proof) { + FluidHolder holder = getHolder(); if (getTemperature() < temperature) return FluidStatus.FAIL_TEMP; - else if (getPressure() < pressure) return FluidStatus.FAIL_PRESSURE; - else if (isGasProof() != proof) return FluidStatus.FAIL_LEAK; + else if (!isGasProof() && proof) return FluidStatus.FAIL_LEAK; + else if (!holder.allowFluid(stack.getFluid())) return FluidStatus.FAIL_CAPACITY; return FluidStatus.SUCCESS; } } diff --git a/src/main/java/tesseract/api/gt/EnergyTileWrapper.java b/src/main/java/tesseract/api/gt/EnergyTileWrapper.java new file mode 100644 index 00000000..9a947188 --- /dev/null +++ b/src/main/java/tesseract/api/gt/EnergyTileWrapper.java @@ -0,0 +1,103 @@ +package tesseract.api.gt; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraftforge.energy.IEnergyStorage; + +public class EnergyTileWrapper implements IGTNode { + + private final TileEntity tile; + private final IEnergyStorage storage; + + private final GTConsumer.State state = new GTConsumer.State(this); + + public EnergyTileWrapper(TileEntity tile, IEnergyStorage storage) { + this.tile = tile; + this.storage = storage; + } + + @Override + public boolean insert(GTTransaction transaction) { + if (storage.getEnergyStored() >= transaction.voltageOut /4) { + transaction.addData(1, 0, this::extractEnergy); + return true; + } + return false; + } + + @Override + public boolean extractEnergy(GTTransaction.TransferData data) { + return storage.extractEnergy((int) (data.getEnergy(1, false) /4), false) > 0; + } + + @Override + public boolean addEnergy(GTTransaction.TransferData data) { + return storage.receiveEnergy((int) (data.getEnergy(1, true) /4), false) > 0; + } + + @Override + public GTTransaction extract(GTTransaction.Mode mode) { + return new GTTransaction(0, 0, a -> { + }); + } + + @Override + public long getEnergy() { + return (long) (storage.getEnergyStored() /4); + } + + @Override + public long getCapacity() { + return (long) (storage.getMaxEnergyStored() * 4); + } + + @Override + public long getOutputAmperage() { + return 0; + } + + @Override + public long getOutputVoltage() { + return 0; + } + + @Override + public long getInputAmperage() { + return 1; + } + + @Override + public long getInputVoltage() { + return Integer.MAX_VALUE; + } + + @Override + public boolean canOutput() { + return false; + } + + @Override + public boolean canInput() { + return storage.canReceive(); + } + + @Override + public boolean canInput(Direction dir) { + return canInput(); + } + + @Override + public boolean canOutput(Direction direction) { + return false; + } + + @Override + public GTConsumer.State getState() { + return state; + } + + @Override + public void tesseractTick() { + getState().onTick(); + } +} \ No newline at end of file diff --git a/src/main/java/tesseract/api/gt/GTConsumer.java b/src/main/java/tesseract/api/gt/GTConsumer.java index 3c72ec2f..ddac356a 100644 --- a/src/main/java/tesseract/api/gt/GTConsumer.java +++ b/src/main/java/tesseract/api/gt/GTConsumer.java @@ -23,28 +23,25 @@ public class GTConsumer extends Consumer { * Creates instance of the consumer. * * @param consumer The consumer node. - * @param path The path information. + * @param path The path information. */ - protected GTConsumer(IGTNode consumer, Path path) { - super(consumer, path); + public GTConsumer(IGTNode consumer,IGTNode producer, Path path) { + super(consumer,producer, path); init(); } /** * Adds energy to the node. Returns quantity of energy that was accepted. - * - * @param maxReceive Amount of energy to be inserted. - * @param simulate If true, the insertion will only be simulated. */ - public void insert(long maxReceive, boolean simulate) { - node.insert(maxReceive, simulate); + public void insert(GTTransaction transaction) { + node.insert(transaction); } /** * @return Gets the amperage required for the consumer. */ - public int getRequiredAmperage(int voltage) { - return (int) Math.min(((node.getCapacity() - node.getEnergy())) / voltage, node.getInputAmperage()); + public long getRequiredAmperage(long voltage) { + return node.availableAmpsInput();//Math.min(((node.getCapacity() - node.getEnergy())) / voltage, node.getInputAmperage()); } /** @@ -63,11 +60,14 @@ public int getLoss() { /** * @param voltage The current voltage. - * @param amperage The current amperage. * @return Checks that the consumer is able to receive energy. */ - public boolean canHandle(int voltage, int amperage) { - return minVoltage >= voltage && minAmperage >= amperage; + public boolean canHandle(long voltage) { + return minVoltage >= voltage; + } + + public boolean canHandleAmp(long minAmperage) { + return this.minAmperage >= minAmperage; } /** @@ -89,4 +89,57 @@ protected void onConnectorCatch(IGTCable cable) { minVoltage = Math.min(minVoltage, cable.getVoltage()); minAmperage = Math.min(minAmperage, cable.getAmps()); } + + public static class State { + long ampsReceived; + long ampsSent; + long euReceived; + long euSent; + public final IGTNode handler; + + public State(IGTNode handler) { + ampsReceived = 0; + euReceived = 0; + this.handler = handler; + } + + public void onTick() { + ampsReceived = 0; + euReceived = 0; + ampsSent = 0; + euSent = 0; + } + + public long extract(boolean simulate, long amps) { + if (handler.canOutput()) { + if (simulate) { + return Math.min(amps, handler.getOutputAmperage() - (ampsSent)); + } + if (ampsSent + amps > handler.getOutputAmperage()) { + return 0; + } + if (!simulate) { + ampsSent += amps; + } + return amps; + } + return 0; + } + + public long receive(boolean simulate, long amps) { + if (handler.canInput()) { + if (simulate) { + return Math.min(amps, handler.getInputAmperage() - (ampsReceived)); + } + if (ampsReceived + amps > handler.getInputAmperage()) { + return 0; + } + if (!simulate) { + ampsReceived += amps; + } + return amps; + } + return 0; + } + } } diff --git a/src/main/java/tesseract/api/gt/GTController.java b/src/main/java/tesseract/api/gt/GTController.java index 76f7a769..0bdeaf15 100644 --- a/src/main/java/tesseract/api/gt/GTController.java +++ b/src/main/java/tesseract/api/gt/GTController.java @@ -1,277 +1,309 @@ package tesseract.api.gt; -import it.unimi.dsi.fastutil.longs.Long2LongLinkedOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2LongMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.objects.*; +import it.unimi.dsi.fastutil.longs.*; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.util.Direction; +import net.minecraft.world.World; +import tesseract.Tesseract; import tesseract.api.ConnectionType; import tesseract.api.Controller; import tesseract.api.ITickingController; +import tesseract.api.capability.ITransactionModifier; import tesseract.graph.*; -import tesseract.util.Dir; import tesseract.util.Node; import tesseract.util.Pos; -import java.util.Comparator; +import javax.annotation.Nonnull; +import java.util.EnumMap; import java.util.List; +import java.util.Map; +import java.util.function.LongConsumer; /** * Class acts as a controller in the group of an electrical components. */ -public class GTController extends Controller implements IGTEvent { +public class GTController extends Controller implements IGTEvent { - private long totalVoltage, totalAmperage, lastVoltage, lastAmperage; - private final Long2LongMap holders = new Long2LongLinkedOpenHashMap(); - private final Object2IntMap obtains = new Object2IntOpenHashMap<>(); - private final Object2ObjectMap> data = new Object2ObjectLinkedOpenHashMap<>(); + private long totalVoltage, totalAmperage, lastVoltage, lastAmperage, totalLoss, lastLoss; + // Cable monitoring. + private Long2LongMap frameHolders = new Long2LongLinkedOpenHashMap(); + private Long2LongMap previousFrameHolder = new Long2LongLinkedOpenHashMap(); + // private final Object2IntMap obtains = new Object2IntOpenHashMap<>(); + private final Long2ObjectMap>> data = new Long2ObjectLinkedOpenHashMap<>(); + + public final LongSet cableIsActive = new LongOpenHashSet(); /** * Creates instance of the controller. - + * * @param dim The dimension id. */ - public GTController(int dim) { - super(dim); + public GTController(World dim, Graph.INodeGetter getter) { + super(dim, getter); } /** * Executes when the group structure has changed. *

- * First, it clears previous controller map, after it lookup for the position of node and looks for the around grids. - * Second, it collects all producers and collectors for the grid and stores it into data map. - * Finally, it will pre-build consumer objects which are available for the producers. So each producer has a list of possible + * First, it clears previous controller map, after it lookup for the position of + * node and looks for the around grids. + * Second, it collects all producers and collectors for the grid and stores it + * into data map. + * Finally, it will pre-build consumer objects which are available for the + * producers. So each producer has a list of possible * consumers with unique information about paths, loss, ect. *

+ * * @see tesseract.graph.Grid (Cache) */ @Override public void change() { - data.clear(); - - for (Long2ObjectMap.Entry> e : group.getNodes().long2ObjectEntrySet()) { - long pos = e.getLongKey(); - IGTNode producer = e.getValue().value(); - - if (producer.canOutput()) { - Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { - if (producer.canOutput(direction)) { - List consumers = new ObjectArrayList<>(); - long side = position.offset(direction).asLong(); - - if (group.getNodes().containsKey(side)) { - onCheck(producer, consumers, null, side); - } else { - Grid grid = group.getGridAt(side, direction); - if (grid != null) { - for (Path path : grid.getPaths(pos)) { - if (!path.isEmpty()) { - Node target = path.target(); - assert target != null; - onCheck(producer, consumers, path, target.asLong()); - } - } - } - } + if (!changeInternal()) { + Tesseract.LOGGER.warn("Error during GTController::change."); + } + } - if (!consumers.isEmpty()) { - if (data.containsKey(producer)) { - onMerge(producer, consumers); - } else { - data.put(producer, consumers); - } + boolean handleInput(long pos, NodeCache producers) { + + for (Map.Entry tup : producers.values()) { + IGTNode producer = tup.getValue(); + Direction direction = tup.getKey(); + if (producer.canOutput(direction)) { + long side = Pos.offset(pos, direction);// position.offset(direction).asLong(); + List consumers = new ObjectArrayList<>(); + + Grid grid = group.getGridAt(side, direction); + if (grid != null) { + for (Path path : grid.getPaths(pos)) { + if (!path.isEmpty()) { + Node target = path.target(); + assert target != null; + if (!onCheck(producer, consumers, path, target.asLong(), target.getDirection())) + return false; } } + } else if (group.getNodes().containsKey(side)) { + onCheck(producer, consumers, null,side, direction.getOpposite()); } + if (!consumers.isEmpty()) + data.computeIfAbsent(pos, m -> new EnumMap<>(Direction.class)) + .put(direction.getOpposite(), consumers); } } + return true; + } - for (List consumers : data.values()) { - consumers.sort(GTConsumer.COMPARATOR); + private boolean changeInternal() { + data.clear(); + for (Long2ObjectMap.Entry> e : group.getNodes().long2ObjectEntrySet()) { + handleInput(e.getLongKey(), e.getValue()); } - } - /** - * Merge the existing consumers with new ones. - * - * @param producer The producer node. - * @param consumers The consumer nodes. - */ - private void onMerge(IGTNode producer, List consumers) { - List existingConsumers = data.get(producer); - for (GTConsumer c : consumers) { - boolean found = false; - for (GTConsumer ec : existingConsumers) { - if (ec.getNode() == c.getNode()) { - found = true; - if (ec.getLoss() > c.getLoss()) { - ec.copy(c); - } - } - if (!found) existingConsumers.add(c); + for (Map> map : data.values()) { + for (List consumers : map.values()) { + consumers.sort(GTConsumer.COMPARATOR); } } + return true; } /** * Adds available consumers to the list. * - * @param producer The producer node. - * @param consumers The consumer nodes. - * @param path The paths to consumers. - * @param pos The position of the producer. + * @param producer The producer node. + * @param consumers The consumer nodes. + * @param path The paths to consumers. + * @param consumerPos The position of the consumer. + * @return whether or not an issue arose checking node. */ - private void onCheck(IGTNode producer, List consumers, Path path, long pos) { - Cache nodee = group.getNodes().get(pos); - if (nodee == null) { - System.out.println("Error in onCheck, null cache."); - return; - } - IGTNode node = nodee.value(); - if (node.canInput()) { - GTConsumer consumer = new GTConsumer(node, path); - int voltage = producer.getOutputVoltage() - consumer.getLoss(); - if (voltage <= 0) { - return; - } + private boolean onCheck(IGTNode producer, List consumers, Path path, long consumerPos, Direction dir) { + NodeCache nodee = group.getNodes().get(consumerPos); - if (voltage <= node.getInputVoltage()) { - consumers.add(consumer); - } else { - onNodeOverVoltage(dim, pos, voltage); + IGTNode node = nodee.value(dir); + + if (node != null && node.canInput(dir)) { + GTConsumer consumer = new GTConsumer(node,producer, path); + long voltage = producer.getOutputVoltage() - consumer.getLoss(); + if (voltage <= 0) { + return true; } + consumers.add(consumer); + return true; } + return true; } /** * Call on the updates to send energy. *

- * Most of the magic going in producer class which acts as wrapper double it around controller map. + * Most of the magic going in producer class which acts as wrapper double it + * around controller map. * Firstly, method will look for the available producer and consumer. - * Secondly, some amperage calculation is going using the consumer and producer data. - * Thirdly, it will check the voltage and amperage for the single impulse by the lowest cost cable. + * Secondly, some amperage calculation is going using the consumer and producer + * data. + * Thirdly, it will check the voltage and amperage for the single impulse by the + * lowest cost cable. *

- * If that function will find corrupted cables, it will execute loop to find the corrupted cables and exit. - * However, if corrupted cables wasn't found, it will looks for variate connection type and store the amp for that path. - * After energy was send, loop will check the amp holder instances on ampers map to find cross-nodes where amps/voltage is exceed max limit. + * If that function will find corrupted cables, it will execute loop to find the + * corrupted cables and exit. + * However, if corrupted cables wasn't found, it will looks for variate + * connection type and store the amp for that path. + * After energy was send, loop will check the amp holder instances on ampers map + * to find cross-nodes where amps/voltage is exceed max limit. */ @Override public void tick() { super.tick(); - holders.clear(); - obtains.clear(); + this.group.connectors().forEach(t -> t.value().setHolder(GTHolder.create(t.value(), 0))); + this.group.getNodes().values().forEach(t -> { + for (Map.Entry n : t.values()) { + n.getValue().tesseractTick(); + break; + } + }); + // obtains.clear(); + } + + @Override + public void insert(long pipePos, Direction side, GTTransaction stack, ITransactionModifier modifier) { + NodeCache node = this.group.getNodes().get(Pos.offset(pipePos, side)); + if (node == null) return; + IGTNode producer = node.value(side.getOpposite()); + Map> map = this.data.get(Pos.offset(pipePos, side)); + if (map == null) + return; + List list = map.get(side); + if (list == null) + return; - for (Object2ObjectMap.Entry> e : data.object2ObjectEntrySet()) { - IGTNode producer = e.getKey(); + long voltage_out = producer.getOutputVoltage(); + long amperage_in = stack.getAvailableAmps(); - // Get the how many amps and energy producer can send - long energy = producer.getEnergy(); - int voltage_out = producer.getOutputVoltage(); - int amperage_in = producer.getOutputAmperage(); - if (amperage_in <= 0) { + if (amperage_in <= 0) { + return; + } + /* + * if (amperage_in <= 0) { // just for sending the last piece of energy + * voltage_out = (int) energy; + * amperage_in = 1; + * } + */ + + for (GTConsumer consumer : list) { + long voltage = voltage_out - consumer.getLoss(); + if (voltage <= 0) { continue; } - amperage_in = (int) Math.min((energy / voltage_out), amperage_in); - if (amperage_in <= 0) { // just for sending the last piece of energy - voltage_out = (int) energy; - amperage_in = 1; - } - for (GTConsumer consumer : e.getValue()) { - int voltage = voltage_out - consumer.getLoss(); - if (voltage <= 0) { - continue; - } + long amperage = consumer.getRequiredAmperage(voltage); + if (amperage <= 0) { // if this consumer received all the energy from the other producers + continue; + } - int amperage = consumer.getRequiredAmperage(voltage); + // Remember amperes stored in this consumer + amperage = Math.min(amperage_in, amperage); + // If we are here, then path had some invalid cables which not suits the limits + // of amps/voltage + stack.addData(amperage, voltage_out - voltage, a -> dataCommit(consumer, a)); + } + } - // Look up how much it already got - int obtained = obtains.getInt(consumer.getNode()); - amperage -= obtained; - if (amperage <= 0) { // if this consumer received all the energy from the other producers - continue; + /** + * Callback from the transaction, that sends data to the consumer and also + * verifies cable voltage/amperage. + * + * @param consumer the consumer. + * @param data the transfer data. + */ + public void dataCommit(GTConsumer consumer, GTTransaction.TransferData data) { + if (!consumer.canHandle(data.getVoltage()) || (consumer.getConnection() == ConnectionType.SINGLE + && !(consumer.canHandleAmp(data.getTotalAmperage())))) { + for (Long2ObjectMap.Entry c : consumer.getFull().long2ObjectEntrySet()) { + long pos = c.getLongKey(); + IGTCable cable = c.getValue(); + switch (cable.getHandler(data.getVoltage(), data.getTotalAmperage())) { + case FAIL_VOLTAGE: + onCableOverVoltage(getWorld(), pos, data.getVoltage()); + return; + case FAIL_AMPERAGE: + onCableOverAmperage(getWorld(), pos, data.getTotalAmperage()); + return; } - - // Remember amperes stored in this consumer - amperage = Math.min(amperage_in, amperage); - obtains.put(consumer.getNode(), amperage + obtained); - - // If we are here, then path had some invalid cables which not suits the limits of amps/voltage - if (consumer.getConnection() != ConnectionType.ADJACENT && !consumer.canHandle(voltage_out, amperage)) { - // Find corrupt cables and return - for (Long2ObjectMap.Entry c : consumer.getFull().long2ObjectEntrySet()) { - long pos = c.getLongKey(); - IGTCable cable = c.getValue(); - - switch (cable.getHandler(voltage_out, amperage)) { - case FAIL_VOLTAGE: - onCableOverVoltage(dim, pos, voltage_out); - break; - case FAIL_AMPERAGE: - onCableOverAmperage(dim, pos, amperage); - break; - } - } + } + } + if (consumer.getConnection() == ConnectionType.VARIATE) { + for (Long2ObjectMap.Entry c : consumer.getCross().long2ObjectEntrySet()) { + long pos = c.getLongKey(); + IGTCable cable = c.getValue(); + cable.setHolder(GTHolder.add(cable.getHolder(), data.getTotalAmperage())); + if (GTHolder.isOverAmperage(cable.getHolder())) { + onCableOverAmperage(getWorld(), pos, GTHolder.getAmperage(cable.getHolder())); return; } - - // Stores the amp into holder for path only for variate connection - if (consumer.getConnection() == ConnectionType.VARIATE) { - for (Long2ObjectMap.Entry c : consumer.getCross().long2ObjectEntrySet()) { - long pos = c.getLongKey(); - IGTCable cable = c.getValue(); - - long holder = holders.get(pos); - holders.put(pos, (holder == 0L) ? GTHolder.create(cable, amperage) : GTHolder.add(holder, amperage)); - } - } - - long amp = amperage; // cast here - long inserted = voltage * amp; - long extracted = voltage_out * amp; - - totalVoltage += extracted; - totalAmperage += amp; - - consumer.insert(inserted, false); - producer.extract(extracted, false); - - amperage_in -= amperage; - if (amperage_in <= 0) { - break; - } } } + cableIsActive.addAll(consumer.getFull().keySet()); - for (Long2LongMap.Entry e : holders.long2LongEntrySet()) { - long pos = e.getLongKey(); - long holder = e.getLongValue(); - - // TODO: Find proper path to destroy - - if (GTHolder.isOverAmperage(holder)) { - onCableOverAmperage(dim, pos, GTHolder.getAmperage(holder)); - } - } + this.totalLoss += data.getLoss(); + this.totalAmperage += data.getTotalAmperage(); + this.totalVoltage += data.getTotalAmperage() * data.getVoltage(); + consumer.getNode().addEnergy(data); } @Override protected void onFrame() { lastVoltage = totalVoltage; lastAmperage = totalAmperage; - totalAmperage = totalVoltage = 0L; + lastLoss = totalLoss; + totalAmperage = totalVoltage = totalLoss = 0L; + previousFrameHolder = frameHolders; + frameHolders = new Long2LongOpenHashMap(); + cableIsActive.clear(); } @Override - public String[] getInfo() { - return new String[]{ - "Total Voltage: ".concat(Long.toString(lastVoltage)), - "Total Amperage: ".concat(Long.toString(lastAmperage)), - }; + public void getInfo(long pos, @Nonnull List list) { + if (this.group != null) { + this.group.getGroupInfo(pos, list); + list.add(String.format("GT Data size: %d", this.data.size())); + } + /* + * int amp = GTHolder.getAmperage(previousFrameHolder.get(pos)); + * return new String[]{ + * "Total Voltage (per tick average): ".concat(Long.toString(lastVoltage / 20)), + * "Total Amperage (per tick average): ".concat(Long.toString(lastAmperage / + * 20)), + * "Cable amperage (last frame): ".concat(Integer.toString(amp)) + * }; + */ + + } + + /** + * GUI SYNC METHODS + **/ + public long getTotalVoltage() { + return lastVoltage; + } + + public long totalAmps() { + return lastAmperage; + } + + public int cableFrameAverage(long pos) { + return GTHolder.getAmperage(previousFrameHolder.get(pos)); } + public long totalLoss() { + return lastLoss; + } + + /** + * END GUI SYNC METHODS + **/ + @Override public ITickingController clone(INode group) { - return new GTController(dim).set(group); + return new GTController(dim, getter).set(group); } } \ No newline at end of file diff --git a/src/main/java/tesseract/api/gt/GTHolder.java b/src/main/java/tesseract/api/gt/GTHolder.java index 265a9570..0d8b92f0 100644 --- a/src/main/java/tesseract/api/gt/GTHolder.java +++ b/src/main/java/tesseract/api/gt/GTHolder.java @@ -8,20 +8,20 @@ public class GTHolder { /** * Creates long with the packed holder. * - * @param cable The cable connector. + * @param cable The cable connector. * @param amperage The initial amperage. */ - protected static long create(IGTCable cable, int amperage) { + public static long create(IGTCable cable, long amperage) { return (long) cable.getAmps() << 32 | amperage; } /** * Adds a new amperage. * - * @param holder The long with the packed holder. + * @param holder The long with the packed holder. * @param amperage The added amperage. */ - protected static long add(long holder, int amperage) { + public static long add(long holder, long amperage) { return (long) getMaxAmperage(holder) << 32 | getAmperage(holder) + amperage; } @@ -29,23 +29,23 @@ protected static long add(long holder, int amperage) { * @param holder The long with the packed holder. * @return Gets a current amperage. */ - protected static int getAmperage(long holder) { - return (int)(holder); + public static int getAmperage(long holder) { + return (int) (holder); } /** * @param holder The long with the packed holder. * @return Gets a maximum amperage. */ - protected static int getMaxAmperage(long holder) { - return (int)(holder >> 32); + public static int getMaxAmperage(long holder) { + return (int) (holder >> 32); } /** * @param holder The long with the packed holder. * @return Checks that the holder is not able to handle it. */ - protected static boolean isOverAmperage(long holder) { + public static boolean isOverAmperage(long holder) { return getMaxAmperage(holder) < getAmperage(holder); } } diff --git a/src/main/java/tesseract/api/gt/GTTransaction.java b/src/main/java/tesseract/api/gt/GTTransaction.java new file mode 100644 index 00000000..410e27a6 --- /dev/null +++ b/src/main/java/tesseract/api/gt/GTTransaction.java @@ -0,0 +1,147 @@ +package tesseract.api.gt; + +import tesseract.api.Transaction; + +import javax.annotation.Nullable; +import java.util.function.Consumer; + +public class GTTransaction extends Transaction { + + public long availableAmps; + public final long voltageOut; + public long eu; + public long usedAmps; + public final Mode mode; + + public GTTransaction(long ampsAvailable, long voltageOut, Consumer consumer) { + super(consumer); + this.availableAmps = ampsAvailable; + this.voltageOut = voltageOut; + this.usedAmps = 0; + this.mode = Mode.TRANSMIT; + } + + public GTTransaction(long eu, Consumer consumer) { + super(consumer); + this.voltageOut = 0; + this.eu = eu; + this.mode = Mode.INTERNAL; + } + + + @Override + public boolean isValid() { + return (this.availableAmps > 0 && this.voltageOut > 0) || this.eu > 0; + } + + @Override + public boolean canContinue() { + return availableAmps > usedAmps || eu > 0; + } + + public long getAvailableAmps() { + return this.availableAmps - this.usedAmps; + } + + public long addAmps(long amps) { + this.availableAmps += amps; + return amps; + } + + public TransferData addData(long amps, long loss, Consumer data) { + TransferData td = new TransferData(this, Math.min(amps, availableAmps - usedAmps), this.voltageOut).setLoss(loss); + this.addData(td); + this.usedAmps += amps; + this.onCommit(data); + return td; + } + + public TransferData addData(long eu, Consumer data) { + eu = Math.min(eu, this.eu); + TransferData dat = this.addData(new TransferData(this, eu)); + this.eu -= eu; + this.onCommit(data); + return dat; + } + + public static class TransferData { + private final long voltage; + private long eu; + private long ampsIn; + private long ampsOut; + private final long totalAmperage; + private long loss; + public final GTTransaction transaction; + + public TransferData(GTTransaction transaction, long amps, long voltage) { + this.ampsIn = this.ampsOut = this.totalAmperage = amps; + this.voltage = voltage; + this.loss = 0; + this.eu = 0; + this.transaction = transaction; + } + + public TransferData(GTTransaction transaction, long eu) { + this.voltage = 0; + this.eu = eu; + this.totalAmperage = 0; + this.transaction = transaction; + } + + public long getEnergy(long amps, boolean input) { + return input ? (voltage - loss) * amps : voltage * amps; + } + + public long getTotalAmperage() { + return totalAmperage; + } + + public long getLoss() { + return loss; + } + + public TransferData setLoss(long loss) { + this.loss = Math.min(this.voltage, loss); + return this; + } + + public long getEu() { + return eu; + } + + public long drainEu(long eu) { + this.eu -= eu; + return eu; + } + + public long getAmps(boolean input) { + return input ? ampsIn : ampsOut; + } + + public void useAmps(boolean input, long amps) { + if (input) { + ampsIn -= amps; + } else { + ampsOut -= amps; + } + } + + @Override + public String toString() { + if (transaction.mode == Mode.INTERNAL) { + return "Internal: " + this.eu; + } else { + return "Transmit amps: " + this.totalAmperage + " voltage: " + this.voltage + " loss: " + this.loss; + } + } + + public long getVoltage() { + return voltage; + } + } + + public enum Mode { + INTERNAL, + TRANSMIT + } +} diff --git a/src/main/java/tesseract/api/gt/IEnergyHandler.java b/src/main/java/tesseract/api/gt/IEnergyHandler.java new file mode 100644 index 00000000..b6887e0b --- /dev/null +++ b/src/main/java/tesseract/api/gt/IEnergyHandler.java @@ -0,0 +1,8 @@ +package tesseract.api.gt; + +import net.minecraft.nbt.CompoundNBT; +import net.minecraftforge.common.util.INBTSerializable; + +public interface IEnergyHandler extends IGTNode, INBTSerializable { + +} diff --git a/src/main/java/tesseract/api/gt/IGTCable.java b/src/main/java/tesseract/api/gt/IGTCable.java index 2133ec4e..ffaf9386 100644 --- a/src/main/java/tesseract/api/gt/IGTCable.java +++ b/src/main/java/tesseract/api/gt/IGTCable.java @@ -7,33 +7,40 @@ */ public interface IGTCable extends IConnectable { - /** - * Returns the energy that this electrical component will permit to lost through or be received in a single tick. - * @return A positive integer representing the loss energy per block, zero or negative indicates that this component doesn't have a loss. - */ - int getLoss(); - - /** - * Returns the maximum amount of packets that this electrical component will permit to pass through or be received in a single tick. - * @return A positive integer representing the maximum packets, zero or negative indicates that this component accepts no energy. - */ - int getAmps(); - - /** - * Returns the maximum energy that this electrical component will permit to pass through or be received in a single packet. - * @return A positive integer representing the maximum accepted energy, zero or negative indicates that this component accepts no energy. - */ - int getVoltage(); - - /** - * @param voltage The current voltage. - * @param amperage The current amperage. - * @return Checks that the cable is able to handle single packet. - */ - default GTStatus getHandler(int voltage, int amperage) { - if (getVoltage() < voltage) return GTStatus.FAIL_VOLTAGE; - else if (getAmps() < amperage) return GTStatus.FAIL_AMPERAGE; - return GTStatus.SUCCESS; - } + /** + * Returns the energy that this electrical component will permit to lost through or be received in a single tick. + * + * @return A positive integer representing the loss energy per block, zero or negative indicates that this component doesn't have a loss. + */ + int getLoss(); + + /** + * Returns the maximum amount of packets that this electrical component will permit to pass through or be received in a single tick. + * + * @return A positive integer representing the maximum packets, zero or negative indicates that this component accepts no energy. + */ + int getAmps(); + + /** + * Returns the maximum energy that this electrical component will permit to pass through or be received in a single packet. + * + * @return A positive integer representing the maximum accepted energy, zero or negative indicates that this component accepts no energy. + */ + int getVoltage(); + + /** + * @param voltage The current voltage. + * @param amperage The current amperage. + * @return Checks that the cable is able to handle single packet. + */ + default GTStatus getHandler(long voltage, long amperage) { + if (getVoltage() < voltage) return GTStatus.FAIL_VOLTAGE; + else if (getAmps() < amperage) return GTStatus.FAIL_AMPERAGE; + return GTStatus.SUCCESS; + } + + long getHolder(); + + void setHolder(long holder); } diff --git a/src/main/java/tesseract/api/gt/IGTEvent.java b/src/main/java/tesseract/api/gt/IGTEvent.java index 6f8111a6..3aa79f70 100644 --- a/src/main/java/tesseract/api/gt/IGTEvent.java +++ b/src/main/java/tesseract/api/gt/IGTEvent.java @@ -1,5 +1,7 @@ package tesseract.api.gt; +import net.minecraft.world.World; + /** * Interface for handling an electric events. (Controller will handle them) */ @@ -7,31 +9,34 @@ public interface IGTEvent { /** * Executes when the node trying to receive higher amount of voltage than can. - * @param dim The dimension id. - * @param pos The node position. + * + * @param dim The dimension id. + * @param pos The node position. * @param voltage The current voltage. */ - default void onNodeOverVoltage(int dim, long pos, int voltage) { + default void onNodeOverVoltage(World world, long pos, long voltage) { //NOOP } /** * Executes when the cable trying to transport higher amount of voltage than can. - * @param dim The dimension id. - * @param pos The cable position. + * + * @param dim The dimension id. + * @param pos The cable position. * @param voltage The current voltage. */ - default void onCableOverVoltage(int dim, long pos, int voltage) { + default void onCableOverVoltage(World world, long pos, long voltage) { //NOOP } /** * Executes when the cable trying to transport higher amount of amperage than can. - * @param dim The dimension id. - * @param pos The cable position. + * + * @param dim The dimension id. + * @param pos The cable position. * @param amperage The current amperage. */ - default void onCableOverAmperage(int dim, long pos, int amperage) { + default void onCableOverAmperage(World world, long pos, long amperage) { //NOOP } } diff --git a/src/main/java/tesseract/api/gt/IGTNode.java b/src/main/java/tesseract/api/gt/IGTNode.java index 7dcc6192..a28aae52 100644 --- a/src/main/java/tesseract/api/gt/IGTNode.java +++ b/src/main/java/tesseract/api/gt/IGTNode.java @@ -1,79 +1,160 @@ package tesseract.api.gt; -import tesseract.api.IConnectable; -import tesseract.util.Dir; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.energy.CapabilityEnergy; +import net.minecraftforge.energy.IEnergyStorage; +import tesseract.api.GraphWrapper; +import tesseract.api.capability.TesseractGTCapability; +import tesseract.graph.Graph; + /** * An electric node is the unit of interaction with electric inventories. *

* Derived from the Redstone Flux power system designed by King Lemming and originally utilized in Thermal Expansion and related mods. * Created with consent and permission of King Lemming and Team CoFH. Released with permission under LGPL 2.1 when bundled with Forge. + * Note: no longer derived from RF. *

*/ -public interface IGTNode extends IConnectable { - - /** - * Adds energy to the node. Returns quantity of energy that was accepted. - * @param maxReceive Maximum amount of energy to be inserted. - * @param simulate If true, the insertion will only be simulated. - * @return Amount of energy that was (or would have been, if simulated) accepted by the storage. - */ - long insert(long maxReceive, boolean simulate); - - /** - * Removes energy from the node. Returns quantity of energy that was removed. - * @param maxExtract Maximum amount of energy to be extracted. - * @param simulate If true, the extraction will only be simulated. - * @return Amount of energy that was (or would have been, if simulated) extracted from the storage. - */ - long extract(long maxExtract, boolean simulate); - - /** - * @return Gets the amount of energy currently stored. - */ - long getEnergy(); - - /** - * @return Gets the maximum amount of energy that can be stored. - */ - long getCapacity(); - - /** - * @return Gets the maximum amount of amperage that can be output. - */ - int getOutputAmperage(); - - /** - * @return Gets the maximum amount of voltage that can be output. - */ - int getOutputVoltage(); - - /** - * @return Gets the maximum amount of amperage that can be input. - */ - int getInputAmperage(); - - /** - * @return Gets the maximum amount of voltage that can be input. - */ - int getInputVoltage(); - - /** - * Gets if this storage can have energy extracted. - * @return If this is false, then any calls to extractEnergy will return 0. - */ - boolean canOutput(); - - /** - * Used to determine if this storage can receive energy. - * @return If this is false, then any calls to receiveEnergy will return 0. - */ - boolean canInput(); - - /** - * Used to determine which sides can output energy (if any). - * @param direction Direction to the output. - * @return Returns true if the given direction is output side. - */ - boolean canOutput(Dir direction); +public interface IGTNode { + + /** + * Adds energy to the node. Returns quantity of energy that was accepted. + */ + default boolean insert(GTTransaction transaction) { + if (transaction.mode == GTTransaction.Mode.TRANSMIT) { + if (!canInput()) return false; + return transaction.addData(Math.min(transaction.getAvailableAmps(), availableAmpsInput()), 0, this::addEnergy).getAmps(true) > 0; + } else { + return transaction.addData(this.getCapacity() - this.getEnergy(), this::addEnergy).getEu() > 0; + } + } + + boolean extractEnergy(GTTransaction.TransferData data); + + boolean addEnergy(GTTransaction.TransferData data); + + /** + * Removes energy from the node. Returns quantity of energy that was removed. + * + * @return Amount of energy that was (or would have been, if simulated) extracted from the storage. + */ + default GTTransaction extract(GTTransaction.Mode mode) { + if (mode == GTTransaction.Mode.TRANSMIT) { + return new GTTransaction(availableAmpsOutput(), this.getOutputVoltage(), this::extractEnergy); + } else if (mode == GTTransaction.Mode.INTERNAL) { + return new GTTransaction(this.getEnergy(), this::extractEnergy); + } + throw new UnsupportedOperationException(); + } + + /** + * @return Gets the amount of energy currently stored. + */ + long getEnergy(); + + /** + * @return Gets the maximum amount of energy that can be stored. + */ + long getCapacity(); + + /** + * @return Gets the maximum amount of amperage that can be output. + */ + long getOutputAmperage(); + + /** + * @return Gets the maximum amount of voltage that can be output. + */ + long getOutputVoltage(); + + /** + * @return Gets the maximum amount of amperage that can be input. + */ + long getInputAmperage(); + + /** + * @return Gets the maximum amount of voltage that can be input. + */ + long getInputVoltage(); + + /** + * Gets if this storage can have energy extracted. + * + * @return If this is false, then any calls to extractEnergy will return 0. + */ + boolean canOutput(); + + /** + * Used to determine if this storage can receive energy. + * + * @return If this is false, then any calls to receiveEnergy will return 0. + */ + boolean canInput(); + + /** + * Used to determine if this storage can receive energy in the given direction. + * + * @param direction the direction. + * @return If this is false, then any calls to receiveEnergy will return 0. + */ + boolean canInput(Direction direction); + + /** + * Used to determine which sides can output energy (if any). + * + * @param direction Direction to the output. + * @return Returns true if the given direction is output side. + */ + boolean canOutput(Direction direction); + + default long availableAmpsOutput() { + if (!canOutput()) return 0; + if (getOutputVoltage() == 0) return 0; + long out = Math.min(getOutputAmperage(), (getEnergy() / getOutputVoltage())); + if (out == -1) out = getOutputAmperage(); + out = Math.min(out, getState().extract(true, out)); + return out; + } + + default long availableAmpsInput() { + if (!canInput()) return 0; + if (getInputVoltage() == 0) return 0; + long out = Math.min(getInputAmperage(), (int) (getCapacity() - getEnergy()) / getInputVoltage()); + if (out == -1) out = getInputAmperage(); + out = Math.min(out, getState().receive(true, out)); + return out; + } + + /** + * Returns the inner state for this node, representing received/sent eu. + * + * @return state. + */ + GTConsumer.State getState(); + + //Called by consumers that cannot tick themselves, such as FE wrappers. + default void tesseractTick() { + + } + + GraphWrapper.ICapabilityGetter GT_GETTER = (level, pos, side, invalidate) -> { + TileEntity tile = level.getBlockEntity(BlockPos.of(pos)); + LazyOptional capability = tile.getCapability(TesseractGTCapability.ENERGY_HANDLER_CAPABILITY, side); + if (capability.isPresent()) { + if (invalidate != null) capability.addListener(t -> invalidate.run()); + return capability.resolve().get(); + } else { + LazyOptional cap = tile.getCapability(CapabilityEnergy.ENERGY, side); + if (cap.isPresent()) { + EnergyTileWrapper node = new EnergyTileWrapper(tile, cap.orElse(null)); + cap.addListener(o -> invalidate.run()); + return node; + } + } + return null; + }; } diff --git a/src/main/java/tesseract/api/item/IItemNode.java b/src/main/java/tesseract/api/item/IItemNode.java index 209da095..b86b9a47 100644 --- a/src/main/java/tesseract/api/item/IItemNode.java +++ b/src/main/java/tesseract/api/item/IItemNode.java @@ -1,8 +1,17 @@ package tesseract.api.item; -import it.unimi.dsi.fastutil.ints.IntList; -import tesseract.api.IConnectable; -import tesseract.util.Dir; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import tesseract.api.GraphWrapper; +import tesseract.graph.Graph; + +import javax.annotation.Nonnull; + /** * An item node is the unit of interaction with item inventories. @@ -12,45 +21,13 @@ * DO NOT ASSUME that these objects are used internally in all cases. *

*/ -public interface IItemNode extends IConnectable { - - /** - * Inserts an item into an available slot and return the remainder. - * @param data ItemData to insert. This must not be modified by the item handler. - * @param simulate If true, the insertion is only simulated - * @return The remaining ItemData that was not inserted (if the entire stack is accepted, then return an empty ItemData). - * May be the same as the input ItemData if unchanged, otherwise a new ItemData. - * The returned ItemData can be safely modified after. - **/ - int insert(ItemData data, boolean simulate); - - /** - * Extracts an item from an available slot. - * @param slot The slot to extract from. - * @param amount Amount to extract (may be greater than the current stack's max limit) - * @param simulate If true, the extraction is only simulated - * @return ItemData extracted from the slot, must be null if nothing can be extracted. - * The returned ItemData can be safely modified after, so item handlers should return a new or copied stack. - **/ - ItemData extract(int slot, int amount, boolean simulate); - - /** - * @param direction The direction index. - * @return Gets all available slots. - **/ - IntList getAvailableSlots(Dir direction); - - /** - * @param direction Direction to the proceed. - * @return Gets the initial amount of items that can be output. - */ - int getOutputAmount(Dir direction); +public interface IItemNode extends IItemHandler { /** * @param direction Direction to the proceed. * @return Returns the priority of this node as a number. */ - int getPriority(Dir direction); + int getPriority(Direction direction); /** * @param slot The slot index. @@ -60,28 +37,130 @@ public interface IItemNode extends IConnectable { /** * Gets if this storage can have item extracted. + * * @return If this is false, then any calls to extractEnergy will return 0. */ boolean canOutput(); /** * Used to determine if this storage can receive item. + * * @return If this is false, then any calls to receiveEnergy will return 0. */ boolean canInput(); + /** + * Used to determine if this storage can receive item. + * + * @return If this is false, then any calls to receiveEnergy will return 0. + */ + boolean canInput(Direction direction); + /** * Used to determine which sides can output item (if any). + * * @param direction Direction to the output. * @return Returns true if the given direction is output side. */ - boolean canOutput(Dir direction); + boolean canOutput(Direction direction); /** * Used to determine which items and at which direction can be consumed. - * @param item The Item to be queried. + * + * @param item The Item to be queried. * @param direction Direction to the input. * @return If the storage can input the item (EVER, not at the time of query). */ - boolean canInput(Object item, Dir direction); + default boolean canInput(ItemStack item, Direction direction) { + return true; + } + + class ItemTileWrapper implements IItemNode { + + private final TileEntity tile; + private final IItemHandler handler; + + public ItemTileWrapper(TileEntity tile, IItemHandler handler) { + this.tile = tile; + this.handler = handler; + } + + @Override + public int getPriority(Direction direction) { + return 0; + } + + @Override + public boolean isEmpty(int slot) { + return handler.getStackInSlot(slot).isEmpty(); + } + + @Override + public boolean canOutput() { + return handler != null; + } + + @Override + public boolean canInput() { + return handler != null; + } + + @Override + public boolean canInput(Direction direction) { + return handler != null; + } + + @Override + public boolean canOutput(Direction direction) { + return true; + } + + @Override + public int getSlots() { + return handler.getSlots(); + } + + @Nonnull + @Override + public ItemStack getStackInSlot(int slot) { + return handler.getStackInSlot(slot); + } + + @Nonnull + @Override + public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) { + return handler.insertItem(slot, stack, simulate); + } + + @Nonnull + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return handler.extractItem(slot, amount, simulate); + } + + @Override + public int getSlotLimit(int slot) { + return handler.getSlotLimit(slot); + } + + @Override + public boolean isItemValid(int slot, @Nonnull ItemStack stack) { + return handler.isItemValid(slot, stack); + } + } + GraphWrapper.ICapabilityGetter GETTER = ((level, pos, capSide, capCallback) -> { + TileEntity tile = level.getBlockEntity(BlockPos.of(pos)); + if (tile == null) { + return null; + } + LazyOptional h = tile.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, capSide); + if (h.isPresent()) { + if (capCallback != null) h.addListener(t -> capCallback.run()); + if (h.map(t -> t instanceof IItemNode).orElse(false)) { + return (IItemNode) h.resolve().get(); + } + return new ItemTileWrapper(tile, h.orElse(null)); + } + return null; + }); } diff --git a/src/main/java/tesseract/api/item/IItemPipe.java b/src/main/java/tesseract/api/item/IItemPipe.java index 40cee594..4a96347b 100644 --- a/src/main/java/tesseract/api/item/IItemPipe.java +++ b/src/main/java/tesseract/api/item/IItemPipe.java @@ -1,7 +1,6 @@ package tesseract.api.item; import tesseract.api.IConnectable; - /** * A item pipe is the unit of interaction with item inventories. */ @@ -9,7 +8,12 @@ public interface IItemPipe extends IConnectable { /** * Returns the maximum amount of items that this item component will permit to pass through or be received in a single tick. + * * @return A positive integer representing the maximum packets, zero or negative indicates that this component accepts no items. */ int getCapacity(); + + int getHolder(); + + void setHolder(int holder); } diff --git a/src/main/java/tesseract/api/item/ItemConsumer.java b/src/main/java/tesseract/api/item/ItemConsumer.java index b4e7be2c..67772625 100644 --- a/src/main/java/tesseract/api/item/ItemConsumer.java +++ b/src/main/java/tesseract/api/item/ItemConsumer.java @@ -1,47 +1,57 @@ package tesseract.api.item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; import tesseract.api.Consumer; import tesseract.graph.Path; -import tesseract.util.Dir; + /** * A class that acts as a container for a item consumer. */ -public class ItemConsumer extends Consumer> { +public class ItemConsumer extends Consumer { private int minCapacity = Integer.MAX_VALUE; - private final Dir input; + private final Direction input; /** * Creates instance of the consumer. * * @param consumer The consumer node. - * @param path The path information. - * @param dir The input direction. + * @param path The path information. + * @param dir The input direction. */ - protected ItemConsumer(IItemNode consumer, Path path, Dir dir) { - super(consumer, path); + public ItemConsumer(IItemNode consumer,IItemNode producer, Path path, Direction dir) { + super(consumer,producer, path); init(); input = dir; } /** * Inserts an item into an available slot and return the remainder. - * @param data ItemData to insert. This must not be modified by the item handler. + * + * @param stack ItemData to insert. This must not be modified by the item handler. * @param simulate If true, the insertion is only simulated * @return The remaining ItemStack that was not inserted (if the entire stack is accepted, then return an empty ItemStack). - * May be the same as the input ItemStack if unchanged, otherwise a new ItemStack. - * The returned ItemStack can be safely modified after. + * May be the same as the input ItemStack if unchanged, otherwise a new ItemStack. + * The returned ItemStack can be safely modified after. **/ - public int insert(ItemData data, boolean simulate) { - return node.insert(data, simulate); + public int insert(ItemStack stack, boolean simulate) { + int count = stack.getCount(); + for (int i = 0; i < node.getSlots(); i++) { + ItemStack inserted = node.insertItem(i, stack, simulate); + if (inserted.getCount() < stack.getCount()) { + return inserted.getCount(); + } + } + return count; } /** * @param item The Item to be queried. * @return If the storage can hold the item (EVER, not at the time of query). */ - public boolean canAccept(Object item) { + public boolean canAccept(ItemStack item) { return node.canInput(item, input); } diff --git a/src/main/java/tesseract/api/item/ItemController.java b/src/main/java/tesseract/api/item/ItemController.java index 536fcad4..bc420f45 100644 --- a/src/main/java/tesseract/api/item/ItemController.java +++ b/src/main/java/tesseract/api/item/ItemController.java @@ -1,204 +1,208 @@ package tesseract.api.item; -import it.unimi.dsi.fastutil.ints.IntIterator; -import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.Tuple; +import net.minecraft.world.World; +import tesseract.api.ConnectionType; import tesseract.api.Consumer; import tesseract.api.Controller; import tesseract.api.ITickingController; +import tesseract.api.capability.ITransactionModifier; import tesseract.graph.*; -import tesseract.util.Dir; import tesseract.util.Node; import tesseract.util.Pos; -import java.util.*; +import javax.annotation.Nonnull; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; /** * Class acts as a controller in the group of an item components. */ -public class ItemController> extends Controller { - +public class ItemController extends Controller { private int transferred; - private final Long2IntMap holders = new Long2IntOpenHashMap(); - private final Object2ObjectMap>>> data = new Object2ObjectLinkedOpenHashMap<>(); + private final Long2ObjectMap>> data = new Long2ObjectLinkedOpenHashMap<>(); /** * Creates instance of the controller. * * @param dim The dimension id. */ - public ItemController(int dim) { - super(dim); - holders.defaultReturnValue(-1); + public ItemController(World dim, Graph.INodeGetter getter) { + super(dim, getter); } @Override - public void change() { - data.clear(); - - for (Long2ObjectMap.Entry> e : group.getNodes().long2ObjectEntrySet()) { - long pos = e.getLongKey(); - N producer = e.getValue().value(); + protected void onFrame() { + this.group.connectors().forEach(t -> t.value().setHolder(0)); + } + protected void handleInput(long pos, NodeCache cache) { + // if (data.containsKey(pos)) return; + for (Map.Entry tup : cache.values()) { + IItemNode producer = tup.getValue(); + Direction direction = tup.getKey(); if (producer.canOutput()) { - Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { - if (producer.canOutput(direction)) { - List> consumers = new ObjectArrayList<>(); - long side = position.offset(direction).asLong(); - - if (group.getNodes().containsKey(side)) { - onCheck(consumers, null, direction.getOpposite(), side); - } else { - Grid grid = group.getGridAt(side, direction); - if (grid != null) { - for (Path path : grid.getPaths(pos)) { - if (!path.isEmpty()) { - Node target = path.target(); - assert target != null; - onCheck(consumers, path, target.getDirection(), target.asLong()); - } - } + if (producer.canOutput(direction)) { + List consumers = new ObjectArrayList<>(); + long side = Pos.offset(pos, direction);// position.offset(direction).asLong(); + Grid grid = group.getGridAt(side, direction); + if (grid != null) { + for (Path path : grid.getPaths(pos)) { + if (!path.isEmpty()) { + Node target = path.target(); + assert target != null; + onCheck(producer, consumers, path, target.getDirection(), target.asLong()); } } + } else if (group.getNodes().containsKey(side)) { + onCheck(producer, consumers, null, direction.getOpposite(), side); + } - if (!consumers.isEmpty()) { - data.computeIfAbsent(producer, m -> new EnumMap<>(Dir.class)).put(direction, consumers); - } + if (!consumers.isEmpty()) { + data.computeIfAbsent(pos, m -> new EnumMap<>(Direction.class)) + .put(direction.getOpposite(), consumers); } } } } + } + + @Override + public void change() { + data.clear(); + + for (Long2ObjectMap.Entry> e : group.getNodes().long2ObjectEntrySet()) { + handleInput(e.getLongKey(), e.getValue()); + } - for (Map>> map : data.values()) { - for (List> consumers : map.values()) { + for (Map> map : data.values()) { + for (List consumers : map.values()) { consumers.sort(Consumer.COMPARATOR); } } } - /** - * Adds available consumers to the list. - * - * @param consumers The consumer nodes. - * @param path The paths to consumers. - * @param dir The added direction. - * @param pos The position of the producer. - */ - private void onCheck(List> consumers, Path path, Dir dir, long pos) { - N node = group.getNodes().get(pos).value(); - if (node.canInput()) consumers.add(new ItemConsumer<>(node, path, dir)); - } - @Override - protected void onFrame() { - transferred = 0; - holders.clear(); - for (Object2ObjectMap.Entry>>> e : data.object2ObjectEntrySet()) { - N producer = e.getKey(); + public void tick() { + super.tick(); + } - for (Map.Entry>> c : e.getValue().entrySet()) { - Dir direction = c.getKey(); + @Override + public void insert(long producerPos, Direction side, ItemTransaction transaction, ITransactionModifier modifier) { + Map> map = this.data.get(Pos.offset(producerPos, side)); + ItemStack stack = transaction.stack; + if (map == null) + return; + List list = map.get(side); + if (list == null) + return; + + // Here the verification starts. + Long2IntMap tempHolders = new Long2IntOpenHashMap(); + for (ItemConsumer consumer : list) { + if (!consumer.canAccept(stack)) { + continue; + } + int amount = consumer.insert(stack, true); + if (amount == stack.getCount()) { + continue; + } + int actual = stack.getCount() - amount; + + if (consumer.getConnection() == ConnectionType.SINGLE) { + actual = actual;//Math.min(actual, consumer.getMinCapacity()); + } else { + // Verify cross chain. + for (Long2ObjectMap.Entry p : consumer.getCross().long2ObjectEntrySet()) { + long pos = p.getLongKey(); + IItemPipe pipe = p.getValue(); + int stacksUsed = pipe.getHolder() + tempHolders.get(pos); + if (pipe.getCapacity() == stacksUsed) { + actual = 0; + break; + } - IntList slots = producer.getAvailableSlots(direction); - if (slots.isEmpty()) { - continue; } + } - IntIterator it = slots.iterator(); - int outputAmount = producer.getOutputAmount(direction); - - I:for (ItemConsumer consumer : c.getValue()) { - while (it.hasNext()) { - int slot = it.nextInt(); - - ItemData data = producer.extract(slot, outputAmount, true); - if (data == null) { - continue; - } - - T stack = data.getStack(); - if (!consumer.canAccept(stack)) { - continue; - } - - int amount = consumer.insert(data, true); - if (amount <= 0) { - continue; - } - - // Stores the pressure into holder for path only for variate connection - switch (consumer.getConnection()) { - case SINGLE: - int min = consumer.getMinCapacity(); // Fast check by the lowest cost pipe - if (min < amount) { - amount = min; - } - break; - - case VARIATE: - int limit = amount; - for (Long2ObjectMap.Entry p : consumer.getCross().long2ObjectEntrySet()) { - long pos = p.getLongKey(); - IItemPipe pipe = p.getValue(); - - int capacity = holders.get(pos); - if (capacity == -1) { - capacity = pipe.getCapacity(); - holders.put(pos, capacity); - } - limit = Math.min(limit, capacity); - } - - if (limit > 0) { - for (long pos : consumer.getCross().keySet()) { - holders.put(pos, Math.max(holders.get(pos) - limit, 0)); - } - } - - amount = limit; - break; - } - - if (amount <= 0) { - continue I; - } - - ItemData extracted = producer.extract(slot, amount, false); - - assert extracted != null; - transferred += amount; - - consumer.insert(extracted, false); - - outputAmount -= amount; - if (outputAmount <= 0) { - break I; - } - - if (producer.isEmpty(slot)) { - it.remove(); - } + if (actual == 0) + continue; + + // Insert the count into the transaction. + ItemStack insert = stack.copy(); + insert.setCount(actual); + modifier.modify(insert, null, side, true); + actual = insert.getCount(); + final int act = actual; + if (act == 0) + continue; + for (Long2ObjectMap.Entry p : consumer.getCross().long2ObjectEntrySet()) { + tempHolders.compute(p.getLongKey(), (a, b) -> { + if (b == null) { + return 1; } - } + return b + 1; + }); } + transaction.addData(insert, t -> dataCommit(consumer, t, side, modifier, act)); + // stack.setCount(stack.getCount() - actual); + if (transaction.stack.getCount() == 0) + return; } } + public void dataCommit(ItemConsumer consumer, ItemStack stack, Direction side, ITransactionModifier modifier, int transferred) { + consumer.insert(stack, false); + this.transferred += transferred; + if (consumer.getConnection() == ConnectionType.VARIATE) { + for (Long2ObjectMap.Entry entry : consumer.getCross().long2ObjectEntrySet()) { + entry.getValue().setHolder(entry.getValue().getHolder()+1); + } + modifier.modify(stack, null, side, true); + } + } + + /** + * Adds available consumers to the list. + * + * @param consumers The consumer nodes. + * @param path The paths to consumers. + * @param dir The added dir. + * @param pos The position of the producer. + */ + private void onCheck(IItemNode producer, List consumers, Path path, Direction dir, long pos) { + IItemNode node = group.getNodes().get(pos).value(dir); + if (node != null && node.canInput(dir)) + consumers.add(new ItemConsumer(node,producer, path, dir)); + } + @Override - public String[] getInfo() { - return new String[]{"Total Transferred: ".concat(Integer.toString(transferred))}; + public void getInfo(long pos, @Nonnull List list) { + if (this.group != null) { + this.group.getGroupInfo(pos, list); + list.add(String.format("Item Data size: %d", this.data.size())); + } + } + + public int getTransferred() { + return transferred; + } + + public int getCableTransferred(long pos) { + return group.getConnector(pos).value().getHolder(); } @Override - public ITickingController clone(INode group) { - return new ItemController<>(dim).set(group); + public ITickingController clone(INode group) { + return new ItemController(dim, getter).set(group); } } diff --git a/src/main/java/tesseract/api/item/ItemData.java b/src/main/java/tesseract/api/item/ItemData.java deleted file mode 100644 index dc7349a8..00000000 --- a/src/main/java/tesseract/api/item/ItemData.java +++ /dev/null @@ -1,35 +0,0 @@ -package tesseract.api.item; - -/** - * A class that acts as a wrapper for a item stack and a slot index. - */ -public class ItemData { - - private final int slot; - private final T stack; - - /** - * Creates instance of the data. - * - * @param slot The slot index. - * @param stack The ItemStack object. - */ - public ItemData(int slot, T stack) { - this.slot = slot; - this.stack = stack; - } - - /** - * @return Gets the slot index. - */ - public int getSlot() { - return slot; - } - - /** - * @return Gets the ItemStack object. - */ - public T getStack() { - return stack; - } -} diff --git a/src/main/java/tesseract/api/item/ItemTransaction.java b/src/main/java/tesseract/api/item/ItemTransaction.java new file mode 100644 index 00000000..86e476d4 --- /dev/null +++ b/src/main/java/tesseract/api/item/ItemTransaction.java @@ -0,0 +1,32 @@ +package tesseract.api.item; + +import net.minecraft.item.ItemStack; +import tesseract.api.Transaction; + +import java.util.function.Consumer; + +public class ItemTransaction extends Transaction { + + public final ItemStack stack; + + public ItemTransaction(ItemStack stack, Consumer consumer) { + super(consumer); + this.stack = stack.copy(); + } + + @Override + public boolean isValid() { + return !stack.isEmpty(); + } + + public void addData(ItemStack count, Consumer consumer) { + this.stack.setCount(this.stack.getCount() - count.getCount()); + this.addData(count); + this.onCommit(consumer); + } + + @Override + public boolean canContinue() { + return !stack.isEmpty(); + } +} diff --git a/src/main/java/tesseract/controller/Energy.java b/src/main/java/tesseract/controller/Energy.java new file mode 100644 index 00000000..9d1b45f7 --- /dev/null +++ b/src/main/java/tesseract/controller/Energy.java @@ -0,0 +1,36 @@ +package tesseract.controller; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.Explosion; +import net.minecraft.world.World; +import tesseract.api.gt.GTController; +import tesseract.api.gt.IGTNode; +import tesseract.graph.Graph; + +// TODO: Make explosions depend on voltage, amp +public class Energy extends GTController { + + /** + * Creates instance of the tesseract.controller. + * + * @param dim The dimension id. + */ + public Energy(World dim, Graph.INodeGetter node) { + super(dim, node); + } + + @Override + public void onNodeOverVoltage(World w, long pos, long voltage) { + Utils.createExplosion(w, BlockPos.of(pos), 4.0F, Explosion.Mode.BREAK); + } + + @Override + public void onCableOverAmperage(World w, long pos, long amperage) { + Utils.createFireAround(w, BlockPos.of(pos)); + } + + @Override + public void onCableOverVoltage(World w, long pos, long voltage) { + Utils.createFireAround(w, BlockPos.of(pos)); + } +} diff --git a/src/main/java/tesseract/controller/Fluid.java b/src/main/java/tesseract/controller/Fluid.java new file mode 100644 index 00000000..b5742eab --- /dev/null +++ b/src/main/java/tesseract/controller/Fluid.java @@ -0,0 +1,58 @@ +package tesseract.controller; + +import net.minecraft.block.Blocks; +import net.minecraft.fluid.Fluids; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvents; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.Explosion; +import net.minecraft.world.World; +import net.minecraftforge.fluids.FluidStack; +import tesseract.api.fluid.FluidController; +import tesseract.api.fluid.IFluidNode; +import tesseract.graph.Graph; + +import javax.annotation.Nonnull; + +// TODO: Make explosions depend on pressure, capacity, temperature +public class Fluid extends FluidController { + + private long lastGasLeakSound = 0; + private static final int GAS_WAIT_TIME = 40; + + /** + * Creates instance of the tesseract.controller. + * + * @param dim The dimension id. + */ + public Fluid(World dim, Graph.INodeGetter get) { + super(dim, get); + } + + @Override + public void onPipeOverPressure(World w, long pos, int pressure, FluidStack fluid) { + Utils.createExplosion(w, BlockPos.of(pos), 4.0F, Explosion.Mode.BREAK); + } + + @Override + public void onPipeOverCapacity(World w, long pos, int capacity, FluidStack fluid) { + Utils.createExplosion(w, BlockPos.of(pos), 1.0F, Explosion.Mode.NONE); + } + + @Override + public void onPipeOverTemp(World w, long pos, int temperature) { + w.setBlockAndUpdate(BlockPos.of(pos), temperature >= Fluids.LAVA.getAttributes().getTemperature() ? Blocks.LAVA.defaultBlockState() : Blocks.FIRE.defaultBlockState()); + } + + @Override + public FluidStack onPipeGasLeak(World world, long pos, @Nonnull FluidStack fluid) { + if (fluid.isEmpty()) return fluid; + FluidStack stack = fluid.copy(); + stack.setAmount((int) ((double) stack.getAmount() * PIPE_LEAK)); + if ((world.getGameTime() - lastGasLeakSound) > GAS_WAIT_TIME) { + world.playSound(null, BlockPos.of(pos), SoundEvents.FIRE_EXTINGUISH, SoundCategory.BLOCKS, 0.3F, 0.9F + world.random.nextFloat() * 0.2F); + lastGasLeakSound = world.getGameTime(); + } + return stack; + } +} diff --git a/src/main/java/tesseract/controller/Utils.java b/src/main/java/tesseract/controller/Utils.java new file mode 100644 index 00000000..9b7828cd --- /dev/null +++ b/src/main/java/tesseract/controller/Utils.java @@ -0,0 +1,32 @@ +package tesseract.controller; + +import net.minecraft.block.Blocks; +import net.minecraft.particles.ParticleTypes; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.Explosion; +import net.minecraft.world.World; +import net.minecraft.world.server.ServerWorld; + +public class Utils { + + public static void createExplosion(World world, BlockPos pos, float explosionRadius, Explosion.Mode modeIn) { + if (world instanceof ServerWorld) { + ServerWorld w = (ServerWorld) world; + w.explode(null, pos.getX(), pos.getY() + 0.0625D, pos.getZ(), explosionRadius, true, modeIn); + w.sendParticles(ParticleTypes.SMOKE, pos.getX(), pos.getY() + 0.5D, pos.getZ(), 1, 0, 0, 0, 0.0D); + } + } + + public static void createFireAround(World world, BlockPos pos) { + boolean fired = false; + for (Direction side : Direction.values()) { + BlockPos offset = pos.relative(side); + if (world.getBlockState(offset) == Blocks.AIR.defaultBlockState()) { + world.setBlockAndUpdate(offset, Blocks.FIRE.defaultBlockState()); + fired = true; + } + } + if (!fired) world.setBlockAndUpdate(pos, Blocks.FIRE.defaultBlockState()); + } +} diff --git a/src/main/java/tesseract/graph/Cache.java b/src/main/java/tesseract/graph/Cache.java index aefabc26..ebb96de5 100644 --- a/src/main/java/tesseract/graph/Cache.java +++ b/src/main/java/tesseract/graph/Cache.java @@ -1,7 +1,7 @@ package tesseract.graph; +import net.minecraft.util.Direction; import tesseract.api.IConnectable; -import tesseract.util.Dir; /** * The Cache is a class that should work with connections. @@ -10,6 +10,7 @@ public class Cache { private final byte connectivity; private final T value; + private NodeCache cache; /** * Creates a cache instance. @@ -19,20 +20,12 @@ public Cache(T value) { this.connectivity = Connectivity.of(value); } - /** - * Creates a cache instance from a delegate. - */ - public Cache(T value, IConnectable delegate) { - this.value = value; - this.connectivity = Connectivity.of(delegate); - } - /** * @param direction The direction index. * @return True when connect, false otherwise. */ - public boolean connects(Dir direction) { - return Connectivity.has(connectivity, direction.getIndex()); + public boolean connects(Direction direction) { + return Connectivity.has(connectivity, direction.get3DDataValue()); } /** @@ -48,4 +41,18 @@ public byte connectivity() { public T value() { return value; } + + public boolean pathing() { + return value.path(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Cache && ((Cache)obj).value == this.value; + } + + @Override + public int hashCode() { + return value.hashCode(); + } } \ No newline at end of file diff --git a/src/main/java/tesseract/graph/Connectivity.java b/src/main/java/tesseract/graph/Connectivity.java index bdffe964..817f3ea0 100644 --- a/src/main/java/tesseract/graph/Connectivity.java +++ b/src/main/java/tesseract/graph/Connectivity.java @@ -1,7 +1,7 @@ package tesseract.graph; +import net.minecraft.util.Direction; import tesseract.api.IConnectable; -import tesseract.util.Dir; /** @@ -18,9 +18,9 @@ public class Connectivity { public static byte of(IConnectable connectable) { byte connectivity = 0; - for (Dir direction : Dir.VALUES) { + for (Direction direction : Direction.values()) { if (connectable.connects(direction)) { - connectivity = Connectivity.set(connectivity, direction.getIndex()); + connectivity = Connectivity.set(connectivity, direction.get3DDataValue()); } } @@ -31,7 +31,7 @@ public static byte of(IConnectable connectable) { * Bitwise set operation. * * @param connectivity The provided state. - * @param side The side index. {@see tesseract.util.Dir} + * @param side The side index. {@see tesseract.util.Dir} * @return Connectivity state for a connection. */ public static byte set(byte connectivity, int side) { @@ -42,7 +42,7 @@ public static byte set(byte connectivity, int side) { * Bitwise clear operation. * * @param connectivity The provided state. - * @param side The side index. {@see tesseract.util.Dir} + * @param side The side index. {@see tesseract.util.Dir} * @return Connectivity state for a connection. */ public static byte clear(byte connectivity, int side) { @@ -53,7 +53,7 @@ public static byte clear(byte connectivity, int side) { * Bitwise toggle operation. * * @param connectivity The provided state. - * @param side The side index. {@see tesseract.util.Dir} + * @param side The side index. {@see tesseract.util.Dir} * @return Connectivity state for a connection. */ public static byte toggle(byte connectivity, int side) { @@ -64,7 +64,7 @@ public static byte toggle(byte connectivity, int side) { * Bitwise check operation. * * @param connectivity The provided state. - * @param side The side index. {@see tesseract.util.Dir} + * @param side The side index. {@see tesseract.util.Dir} * @return True if a connection is exist, false otherwise. */ public static boolean has(byte connectivity, int side) { diff --git a/src/main/java/tesseract/graph/Graph.java b/src/main/java/tesseract/graph/Graph.java index d89fc6f2..84f53c78 100644 --- a/src/main/java/tesseract/graph/Graph.java +++ b/src/main/java/tesseract/graph/Graph.java @@ -4,11 +4,11 @@ import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.util.Direction; import tesseract.api.Controller; import tesseract.api.IConnectable; -import tesseract.util.Dir; -import tesseract.util.Pos; import tesseract.util.CID; +import tesseract.util.Pos; import java.util.List; import java.util.function.Supplier; @@ -16,241 +16,243 @@ /** * Class provides the functionality of any set of nodes. */ -public class Graph implements INode { - - private final Int2ObjectMap> groups = new Int2ObjectLinkedOpenHashMap<>(); - private final Long2IntMap positions = new Long2IntLinkedOpenHashMap(); // group positions - - public Graph() { - positions.defaultReturnValue(CID.INVALID); - } - - @Override - public boolean contains(long pos) { - return positions.containsKey(pos); - } - - @Override - public boolean linked(long from, Dir towards, long to) { - return positions.containsKey(from) && positions.containsKey(to) && positions.get(from) == positions.get(to); - } - - @Override - public boolean connects(long pos, Dir towards) { - return contains(pos); - } - - /** - * @return Gets the size of the groups map. - */ - public int countGroups() { - return groups.size(); - } - - /** - * @return Gets the groups map. - */ - public Int2ObjectMap> getGroups() { - return Int2ObjectMaps.unmodifiable(groups); - } - - /** - * Adds a node to the graph at the specified position. - * - * @param pos The position at which the node will be added. - * @param node The node to add. - * @param controller The controller to use. - * @return True on success or false otherwise. - */ - public boolean addNode(long pos, Cache node, Controller controller) { - if (!contains(pos)) { - Group group = add(pos, () -> Group.singleNode(pos, node, controller)); - if (group != null) group.addNode(pos, node, controller); - return true; - } - - return false; - } - - /** - * Adds a connector to the graph at the specified position. - * - * @param pos The position at which the node will be added. - * @param connector The connector to add. - * @param controller The controller to use. - * @return True on success or false otherwise. - */ - public boolean addConnector(long pos, Cache connector, Controller controller) { - if (!contains(pos)) { - Group group = add(pos, () -> Group.singleConnector(pos, connector, controller)); - if (group != null) group.addConnector(pos, connector, controller); - return true; - } - - return false; - } - - /** - * Adds an item to the Graph, in a manner generic across nodes and connectors. - * - * @param pos The position at which the item will be added. - * @param single A group containing a single entry, if the position is not touching any existing positions. - * @return An existing group, that the caller should add the entry to. - */ - private Group add(long pos, Supplier> single) { - int id; - IntSet mergers = getNeighboringGroups(pos); - switch (mergers.size()) { - case 0: - id = CID.nextId(); - positions.put(pos, id); - groups.put(id, single.get()); - return null; - - case 1: - id = mergers.iterator().nextInt(); - positions.put(pos, id); - return groups.get(id); - - default: - Merged data = beginMerge(mergers); - positions.put(pos, data.bestId); - for (Group other : data.merged) { - data.best.mergeWith(other, pos); - } - return data.best; - } - } - - /** - * Removes an entry from the Group, potentially splitting it if needed. By calling this function, the caller asserts - * that this group contains the specified position; the function may misbehave if the group does not actually contain - * the specified position. - * - * @param pos The position of the entry to remove. - */ - public void removeAt(long pos) { - int id = positions.remove(pos); - - if (id == CID.INVALID) { - return; - } - - Group group = groups.get(id); - - group.removeAt(pos, newGroup -> { - int newId = CID.nextId(); - groups.put(newId, newGroup); - - // Mark the nodes as pointing at the new group - for (long part : newGroup.getNodes().keySet()) { - positions.put(part, newId); - } - - // Mark the connectors as pointing at the new group - for (Grid grid : newGroup.getGrids().values()) { - for (long part : grid.getConnectors().keySet()) { - positions.put(part, newId); - } - } - }); - - if (group.countBlocks() == 0) { - groups.remove(id); - } - } - - /** - * Gets the group by a given position. - * - * @param pos The position of the group. - * @return The group, guaranteed to not be null. - */ - public Group getGroupAt(long pos) { - int id = positions.get(pos); - return (id != CID.INVALID) ? groups.get(id) : null; - } - - /** - * Starts a merging process for a given groups. - * - * @param mergers An array of neighbors groups id. - * @return The wrapper with groups which should be merged. - */ - private Merged beginMerge(IntSet mergers) { - int bestId = mergers.iterator().nextInt(); - Group best = groups.get(bestId); - int bestSize = best.countBlocks(); - - for (int id : mergers) { - Group candidate = groups.get(id); - int size = candidate.countBlocks(); - - if (size > bestSize) { - best = candidate; - bestId = id; - bestSize = size; - } - } - - List> mergeGroups = new ObjectArrayList<>(mergers.size() - 1); - - for (int id : mergers) { - if (id == bestId) { - continue; - } - - Group removed = groups.remove(id); - - // Remap each position to point to the correct group. - for (long pos : removed.getBlocks()) { - positions.put(pos, bestId); - } - - mergeGroups.add(removed); - } - - return new Merged<>(bestId, best, mergeGroups); - } - - /** - * Lookups for neighbors groups around given position. - * - * @param pos The search position. - * @return The set of the groups which are neighbors to each other. - */ - private IntSet getNeighboringGroups(long pos) { - IntSet neighbors = new IntLinkedOpenHashSet(6); - - Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { - long side = position.offset(direction).asLong(); - int id = positions.get(side); - - if (id != CID.INVALID) { - neighbors.add(id); - } - } - - return neighbors; - } - - /** - * @apiNote Wrapper for merged groups. - */ - private static class Merged { - - final int bestId; - final Group best; - final List> merged; - - /** - * Constructs a new Merged of the groups. - */ - Merged(int bestId, Group best, List> merged) { - this.best = best; - this.bestId = bestId; - this.merged = merged; - } - } +public class Graph implements INode { + + public static final Direction[] DIRECTIONS = Direction.values(); + private final Int2ObjectMap> groups = new Int2ObjectLinkedOpenHashMap<>(); + private final Long2IntMap positions = new Long2IntLinkedOpenHashMap(); // group positions + private final Supplier> controller; + + public Graph(Supplier> controller) { + positions.defaultReturnValue(CID.INVALID); + this.controller = controller; + } + + @Override + public boolean contains(long pos) { + return positions.containsKey(pos); + } + + @Override + public boolean linked(long from, Direction towards, long to) { + return positions.containsKey(from) && positions.containsKey(to) && positions.get(from) == positions.get(to); + } + + @Override + public boolean connects(long pos, Direction towards) { + return contains(pos); + } + + /** + * @return Gets the size of the groups map. + */ + public int countGroups() { + return groups.size(); + } + + /** + * @return Gets the groups map. + */ + public Int2ObjectMap> getGroups() { + return Int2ObjectMaps.unmodifiable(groups); + } + + public void addNode(long pos, NodeCache cache) { + if (cache.count() == 0) return; + Group group = add(pos, () -> Group.singleNode(pos, cache, controller.get())); + if (group != null) + group.addNode(pos, cache, controller.get()); + } + + /** + * Primary Tesseract interaction. Adds a connector to the graph at the specified position while adding listeners to blocks + * around it. + * + * @param pos the connector position. + * @param connector the cached connector. + */ + public void addConnector(long pos, Cache connector) { + if (!contains(pos)) { + Group group = add(pos, () -> Group.singleConnector(pos, connector, controller.get())); + if (group != null) + group.addConnector(pos, connector, controller.get()); + } + } + + /** + * Adds an item to the Graph, in a manner generic across nodes and connectors. + * + * @param pos The position at which the item will be added. + * @param single A group containing a single entry, if the position is not + * touching any existing positions. + * @return An existing group, that the caller should add the entry to. + */ + private Group add(long pos, Supplier> single) { + int id; + IntSet mergers = getNeighboringGroups(pos); + switch (mergers.size()) { + case 0 : { + id = CID.nextId(); + positions.put(pos, id); + groups.put(id, single.get()); + return null; + } + case 1 : { + id = mergers.iterator().nextInt(); + positions.put(pos, id); + return groups.get(id); + } + default : { + Merged data = beginMerge(mergers); + positions.put(pos, data.bestId); + for (Group other : data.merged) { + data.best.mergeWith(other, pos); + } + return data.best; + } + } + } + + /** + * Removes an entry from the Group, potentially splitting it if needed. By + * calling this function, the caller asserts that this group contains the + * specified position; the function may misbehave if the group does not actually + * contain the specified position. + * + * @param pos The position of the entry to remove. + */ + public boolean removeAt(long pos) { + return removeInternal(pos); + } + + private boolean removeInternal(long pos) { + int id = positions.get(pos); + + if (id == CID.INVALID) { + return false; + } + Group group = groups.get(id); + + boolean ok = group.removeAt(pos, newGroup -> { + int newId = CID.nextId(); + groups.put(newId, newGroup); + + // Mark the nodes as pointing at the new group + for (long part : newGroup.getNodes().keySet()) { + positions.put(part, newId); + } + + // Mark the connectors as pointing at the new group + for (Grid grid : newGroup.getGrids().values()) { + for (long part : grid.getConnectors().keySet()) { + positions.put(part, newId); + } + } + }); + if (ok) { + positions.remove(pos); + } + if (group.countBlocks() == 0) { + groups.remove(id); + } + return ok; + } + + /** + * Gets the group by a given position. + * + * @param pos The position of the group. + * @return The group, guaranteed to not be null. + */ + public Group getGroupAt(long pos) { + int id = positions.get(pos); + return (id != CID.INVALID) ? groups.get(id) : null; + } + + /** + * Starts a merging process for a given groups. + * + * @param mergers An array of neighbors groups id. + * @return The wrapper with groups which should be merged. + */ + private Merged beginMerge(IntSet mergers) { + int bestId = mergers.iterator().nextInt(); + Group best = groups.get(bestId); + int bestSize = best.countBlocks(); + + for (int id : mergers) { + Group candidate = groups.get(id); + int size = candidate.countBlocks(); + + if (size > bestSize) { + best = candidate; + bestId = id; + bestSize = size; + } + } + + List> mergeGroups = new ObjectArrayList<>(mergers.size() - 1); + + for (int id : mergers) { + if (id == bestId) { + continue; + } + + Group removed = groups.remove(id); + + // Remap each position to point to the correct group. + for (long pos : removed.getBlocks()) { + positions.put(pos, bestId); + } + + mergeGroups.add(removed); + } + + return new Merged<>(bestId, best, mergeGroups); + } + + /** + * Lookups for neighbors groups around given position. + * + * @param pos The search position. + * @return The set of the groups which are neighbors to each other. + */ + private IntSet getNeighboringGroups(long pos) { + IntSet neighbors = new IntLinkedOpenHashSet(6); + + Pos position = new Pos(pos); + for (Direction direction : Graph.DIRECTIONS) { + long side = position.offset(direction).asLong(); + int id = positions.get(side); + + if (id != CID.INVALID) { + neighbors.add(id); + } + } + + return neighbors; + } + + /** + * @apiNote Wrapper for merged groups. + */ + private static class Merged { + + final int bestId; + final Group best; + final List> merged; + + /** + * Constructs a new Merged of the groups. + */ + Merged(int bestId, Group best, List> merged) { + this.best = best; + this.bestId = bestId; + this.merged = merged; + } + } + + public interface INodeGetter { + T get(long pos, Direction capSide, Runnable capCallback); + } } diff --git a/src/main/java/tesseract/graph/Grid.java b/src/main/java/tesseract/graph/Grid.java index 271d647c..092c072b 100644 --- a/src/main/java/tesseract/graph/Grid.java +++ b/src/main/java/tesseract/graph/Grid.java @@ -1,15 +1,25 @@ package tesseract.graph; -import it.unimi.dsi.fastutil.longs.*; -import it.unimi.dsi.fastutil.objects.*; -import tesseract.api.IConnectable; -import tesseract.util.*; -import tesseract.graph.traverse.ASFinder; -import tesseract.graph.traverse.BFDivider; - import java.util.Deque; import java.util.List; import java.util.function.Consumer; +import java.util.function.LongConsumer; +import java.util.function.LongPredicate; + +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.util.Direction; +import tesseract.api.IConnectable; +import tesseract.graph.traverse.ASFinder; +import tesseract.graph.traverse.BFDivider; +import tesseract.util.Node; +import tesseract.util.Pos; +import tesseract.util.SetUtil; /** * Grid provides the functionality of a set of linked nodes. @@ -17,17 +27,17 @@ public class Grid implements INode { private final Long2ObjectMap> connectors = new Long2ObjectLinkedOpenHashMap<>(); - private final Long2ByteMap nodes = new Long2ByteLinkedOpenHashMap(); + private final Long2ObjectMap> nodes = new Long2ObjectLinkedOpenHashMap<>(); private final BFDivider divider = new BFDivider(this); private final ASFinder finder = new ASFinder(this); // Prevent the creation of empty grids externally, a caller needs to use singleConnector. private Grid() { - nodes.defaultReturnValue(Byte.MAX_VALUE); + } /** - * @param pos The position of the connector. + * @param pos The position of the connector. * @param connector The given connector. * @return Create a instance of a class for a given position and connector. */ @@ -43,50 +53,53 @@ public boolean contains(long pos) { } @Override - public boolean linked(long from, Dir towards, long to) { + public boolean linked(long from, Direction towards, long to) { assert towards != null; Cache cacheFrom = connectors.get(from); Cache cacheTo = connectors.get(to); - byte connectivityFrom = nodes.get(from); - byte connectivityTo = nodes.get(to); + byte connectivityFrom; + byte connectivityTo; boolean validLink = false; + if (cacheFrom != null) { validLink = true; connectivityFrom = cacheFrom.connectivity(); + } else { + NodeCache cache = nodes.get(from); + connectivityFrom = cache == null ? 0 : Connectivity.of(cache); } if (cacheTo != null) { validLink = true; connectivityTo = cacheTo.connectivity(); + } else { + NodeCache cache = nodes.get(to); + connectivityTo = cache == null ? 0 : Connectivity.of(cache); } - if (connectivityFrom == Byte.MAX_VALUE || connectivityTo == Byte.MAX_VALUE) { + if (connectivityFrom == 0 && connectivityTo == 0) { return false; } - return validLink && Connectivity.has(connectivityFrom, towards.getIndex()) && Connectivity.has(connectivityTo, towards.getOpposite().getIndex()); + return validLink && Connectivity.has(connectivityFrom, towards.get3DDataValue()) && Connectivity.has(connectivityTo, towards.getOpposite().get3DDataValue()); } @Override - public boolean connects(long pos, Dir towards) { + public boolean connects(long pos, Direction towards) { assert towards != null; - Cache cache = connectors.get(pos); - byte connectivity = nodes.get(pos); if (cache != null) { - connectivity = cache.connectivity(); + byte connectivity = cache.connectivity(); + return Connectivity.has(connectivity, towards.get3DDataValue()); + } else { + NodeCache c = nodes.get(pos); + return c != null && c.connects(towards); } - - if (connectivity == Byte.MAX_VALUE) { - return false; - } - - return Connectivity.has(connectivity, towards.getIndex()); } /** @@ -111,10 +124,10 @@ public Long2ObjectMap> getConnectors() { } /** - * @return Returns nodes map. + * @return Returns nodes map, excluding connectors. */ - public Long2ByteMap getNodes() { - return Long2ByteMaps.unmodifiable(nodes); + public Long2ObjectMap> getNodes() { + return Long2ObjectMaps.unmodifiable(nodes); } /** @@ -125,13 +138,11 @@ public Long2ByteMap getNodes() { */ public List> getPaths(long from) { List> data = new ObjectArrayList<>(); - - for (long to : nodes.keySet()) { - if (from != to) { + nodes.keySet().forEach((LongConsumer) to -> { + if (to != from) { data.add(new Path<>(connectors, finder.traverse(from, to))); } - } - + }); return data; } @@ -153,7 +164,7 @@ public Deque getPath(long origin, long target) { */ public void mergeWith(Grid other) { connectors.putAll(other.connectors); - nodes.putAll(other.nodes); + this.nodes.putAll(other.nodes); } /** @@ -169,7 +180,7 @@ public long sampleConnector() { /** * Adds a new connector to the grid. * - * @param pos The given position. + * @param pos The given position. * @param connector The given connector. */ public void addConnector(long pos, Cache connector) { @@ -180,10 +191,9 @@ public void addConnector(long pos, Cache connector) { * Adds a new node to the grid. * * @param pos The given position. - * @param node The given node. */ - public void addNode(long pos, Cache node) { - nodes.put(pos, node.connectivity()); + public void addNode(long pos, NodeCache cache) { + nodes.put(pos, cache); } /** @@ -200,7 +210,7 @@ public void removeNode(long pos) { * that this group contains the specified position; the function may misbehave if the group does not actually contain * the specified position. * - * @param pos The position of the entry to remove. + * @param pos The position of the entry to remove. * @param split A consumer for the resulting fresh graphs from the split operation. */ public void removeAt(long pos, Consumer> split) { @@ -217,18 +227,18 @@ public void removeAt(long pos, Consumer> split) { List colored = new ObjectArrayList<>(); int bestColor = divider.divide( - removed -> removed.add(pos), - roots -> { - Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { - long side = position.offset(direction).asLong(); - - if (linked(pos, direction, side)) { - roots.add(side); + removed -> removed.add(pos), + roots -> { + Pos position = new Pos(pos); + for (Direction direction : Graph.DIRECTIONS) { + long side = position.offset(direction).asLong(); + + if (linked(pos, direction, side)) { + roots.add(side); + } } - } - }, - colored::add + }, + colored::add ); LongSet check = new LongLinkedOpenHashSet(); @@ -243,11 +253,9 @@ public void removeAt(long pos, Consumer> split) { LongSet found = colored.get(i); for (long reached : found) { - byte connectivity = nodes.get(reached); - - if (connectivity != Byte.MAX_VALUE) { + if (nodes.containsKey(reached)) { check.add(reached); - newGrid.nodes.put(reached, connectivity); + newGrid.nodes.put(reached, this.nodes.get(reached)); } else { newGrid.connectors.put(reached, connectors.remove(reached)); } @@ -271,12 +279,10 @@ public void removeAt(long pos, Consumer> split) { */ private void removeFinal(long pos) { connectors.remove(pos); + for (Direction direction : Graph.DIRECTIONS) { + long side = Pos.offset(pos, direction); - Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { - long side = position.offset(direction).asLong(); - - if (nodes.containsKey(side) && isExternal(side)) { + if (nodes.containsKey(side) && isExternal(side) && this.nodes.get(side).connects(direction.getOpposite())) { nodes.remove(side); } } @@ -295,9 +301,8 @@ private boolean isExternal(long pos) { } int neighbors = 0; - Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { - long side = position.offset(direction).asLong(); + for (Direction direction : Graph.DIRECTIONS) { + long side = Pos.offset(pos, direction); if (!nodes.containsKey(side) && linked(pos, direction, side)) { neighbors++; diff --git a/src/main/java/tesseract/graph/Group.java b/src/main/java/tesseract/graph/Group.java index 9d292b14..69a9c3b9 100644 --- a/src/main/java/tesseract/graph/Group.java +++ b/src/main/java/tesseract/graph/Group.java @@ -1,34 +1,39 @@ package tesseract.graph; -import it.unimi.dsi.fastutil.ints.*; +import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.longs.*; -import it.unimi.dsi.fastutil.objects.*; -import org.apache.commons.collections4.SetUtils; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import tesseract.Tesseract; import tesseract.api.Controller; import tesseract.api.IConnectable; -import tesseract.api.ITickHost; import tesseract.api.ITickingController; import tesseract.graph.traverse.BFDivider; -import tesseract.util.Dir; -import tesseract.util.Pos; import tesseract.util.CID; +import tesseract.util.Pos; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.function.Consumer; +import java.util.stream.LongStream; +import java.util.stream.Stream; /** * Group provides the functionality of a set of adjacent nodes that may or may not be linked. */ -public class Group implements INode { +public class Group implements INode { - private final Long2ObjectMap> nodes = new Long2ObjectLinkedOpenHashMap<>(); + private final Long2ObjectMap> nodes = new Long2ObjectLinkedOpenHashMap<>(); private final Int2ObjectMap> grids = new Int2ObjectLinkedOpenHashMap<>(); private final Long2IntMap connectors = new Long2IntLinkedOpenHashMap(); // connectors pairing private final BFDivider divider = new BFDivider(this); - private ITickingController controller = null; - private ITickHost currentTickHost = null; + private ITickingController controller = null; // Prevent the creation of empty groups externally, a caller needs to use singleNode/singleConnector. private Group() { @@ -36,147 +41,80 @@ private Group() { } /** - * @param pos The position of the node. - * @param node The given node. + * @param pos The position of the node. + * @param node The given node. * @param controller The given controller. * @return Create a instance of a class for a given position and node. */ - protected static Group singleNode(long pos, Cache node, Controller controller) { - Group group = new Group<>(); + protected static Group singleNode(long pos, NodeCache node, Controller controller) { + Group group = new Group<>(); group.addNode(pos, node, controller); return group; } /** - * @param pos The position of the connector. - * @param connector The given connector. + * @param pos The position of the connector. + * @param connector The given connector. * @param controller The given controller. * @return Create a instance of a class for a given position and connector. */ - protected static Group singleConnector(long pos, Cache connector, Controller controller) { - Group group = new Group<>(); + protected static Group singleConnector(long pos, Cache connector, Controller controller) { + Group group = new Group<>(); int id = CID.nextId(); group.connectors.put(pos, id); group.grids.put(id, Grid.singleConnector(pos, connector)); - group.updateController(connector, controller); + group.updateController(controller); return group; } + public Iterable> connectors() { + return () -> this.grids.values().stream().flatMap(t -> t.getConnectors().values().stream()).distinct().iterator(); + } + + public Iterable>> connectorsEntries() { + return () -> this.grids.values().stream().flatMap(t -> t.getConnectors().long2ObjectEntrySet().stream()).distinct().iterator(); + } + + public LongStream pipeNodes() { + return this.connectors.long2IntEntrySet().stream().mapToLong(t -> this.grids.get(t.getIntValue()).getConnectors().get(t.getLongKey()).pathing() ? t.getLongKey() : Long.MIN_VALUE).filter(l -> l != Long.MIN_VALUE); + } + + @Override public boolean contains(long pos) { return nodes.containsKey(pos) || connectors.containsKey(pos); } @Override - public boolean linked(long from, Dir towards, long to) { + public boolean linked(long from, Direction towards, long to) { return contains(from) && contains(to); } @Override - public boolean connects(long pos, Dir towards) { + public boolean connects(long pos, Direction towards) { return contains(pos); } - /** - * Resets the current tick host. - */ - private void releaseControllerHost() { - if (currentTickHost != null && controller != null) { - currentTickHost.reset(controller, null); - } - } - - /** - * Resets the current controller host. - * - * @param cache The given cache. - */ - private void resetControllerHost(Cache cache) { - if (currentTickHost != null && cache.value() instanceof ITickHost && cache.value() == currentTickHost) { - currentTickHost.reset(controller, null); - findNextValidHost(cache); - } - else if (controller != null) { - controller.change(); - } - } - /** * Calls the changing method for the controller. * - * @param cache The given cache object. * @param ticking The ticking instance. */ - private void updateController(Cache cache, Controller ticking) { - if (ticking == null) return; + private void updateController(Controller ticking) { + if (ticking == null) + return; if (controller == null) { ticking.set(this); controller = ticking; } - - if (currentTickHost == null) { - // If cache contains tick host set as a new one - if (cache.value() instanceof ITickHost) { - currentTickHost = (ITickHost) cache.value(); - currentTickHost.reset(null, controller); + if (Tesseract.hadFirstTick(controller.getWorld())) { + try { + controller.change(); + } catch (Exception ex) { + Tesseract.LOGGER.warn("Error updating controller : " + ex); } } - - controller.change(); - } - - /** - * Finds the next available host in the group. - * - * @param cache The given cache. - */ - private void findNextValidHost(Cache cache) { - if (controller == null) return; - currentTickHost = null; - - // Lookup for a ticking host among nodes - for (Cache n : nodes.values()) { - if (nextCache(cache, n)) { - continue; - } - break; - } - - if (currentTickHost == null) { - // Lookup for a ticking host among connectors - I: for (int id : connectors.values()) { - Grid grid = grids.get(id); - - for (Cache c : grid.getConnectors().values()) { - if (nextCache(cache, c)) { - continue; - } - break I; - } - } - } - - if (currentTickHost != null) { - currentTickHost.reset(null, controller); - controller.change(); - } - } - - /** - * Trying to switch for a new host. - * - * @param cache The given cache object. - * @param o The current cache. - * @return True or false. - */ - private boolean nextCache(Cache cache, Cache o) { - if (o == cache || !(o.value() instanceof ITickHost)) { - return true; - } - - currentTickHost = (ITickHost) o.value(); - return false; } /** @@ -190,13 +128,15 @@ public int countBlocks() { * @return Returns blocks set. */ public Set getBlocks() { - return SetUtils.union(nodes.keySet(), connectors.keySet()); + Set copy = new ObjectOpenHashSet<>(nodes.keySet()); + copy.addAll(connectors.keySet()); + return copy; } /** * @return Returns nodes map. */ - public Long2ObjectMap> getNodes() { + public Long2ObjectMap> getNodes() { return Long2ObjectMaps.unmodifiable(nodes); } @@ -210,66 +150,55 @@ public Int2ObjectMap> getGrids() { /** * @return Returns group controller. */ - public ITickingController getController() { + public ITickingController getController() { return controller; } - /** - * @return Returns group ticking host. - */ - public ITickHost getCurrentTickHost() { - return currentTickHost; - } - /** * Adds a new node to the group. * - * @param pos The given position. - * @param node The given node. + * @param pos The given position. + * @param node The given node. * @param controller The controller to use. */ - public void addNode(long pos, Cache node, Controller controller) { + public void addNode(long pos, NodeCache node, Controller controller) { nodes.put(pos, node); Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { - if (!node.connects(direction)) { + for (Direction direction : Graph.DIRECTIONS) { + int connector = connectors.get(position.offset(direction).asLong()); + if (connector == CID.INVALID) { continue; } - - long side = position.offset(direction).asLong(); - int id = connectors.get(side); - - // Add a node to the neighboring grid ? - if (id != CID.INVALID) { - Grid grid = grids.get(id); - side = position.offset(direction).asLong(); - - if (grid.connects(side, direction.getOpposite())) { - grid.addNode(pos, node); - } + Grid grid = grids.get(connector); + if (!grid.connects(position.offset(direction).asLong(), direction.getOpposite())) { + continue; } + + grid.addNode(pos, node); } - updateController(node, controller); + updateController(controller); } /** * Adds a new connector to the group. - * @param pos The given position. - * @param connector The given connector. + * + * @param pos The given position. + * @param connector The given connector. * @param controller The controller to use. */ - public void addConnector(long pos, Cache connector, Controller controller) { + public void addConnector(long pos, Cache connector, Controller controller) { Int2ObjectMap> linked = new Int2ObjectLinkedOpenHashMap<>(); - Long2ObjectMap joined = new Long2ObjectLinkedOpenHashMap<>(); + Long2ObjectMap joined = new Long2ObjectLinkedOpenHashMap<>(); Grid bestGrid = null; int bestCount = 0; + int bestId = CID.INVALID; Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { + for (Direction direction : Graph.DIRECTIONS) { if (!connector.connects(direction)) { continue; } @@ -304,6 +233,7 @@ public void addConnector(long pos, Cache connector, Controller controll bestGrid = Grid.singleConnector(pos, connector); connectors.put(pos, bestId); + grids.put(bestId, bestGrid); bestCount = -1; // For exit } @@ -313,14 +243,11 @@ public void addConnector(long pos, Cache connector, Controller controll } // Add neighbours nodes to the grid - for (Long2ObjectMap.Entry e : joined.long2ObjectEntrySet()) { + for (Long2ObjectMap.Entry e : joined.long2ObjectEntrySet()) { long move = e.getLongKey(); - Dir direction = e.getValue(); - - Cache node = nodes.get(move); - - if (node.connects(direction.getOpposite())) { - bestGrid.addNode(move, node); + Direction direction = e.getValue(); + if (connector.connects(direction)) { + bestGrid.addNode(move, nodes.get(move)); } } @@ -348,20 +275,10 @@ public void addConnector(long pos, Cache connector, Controller controll } } } - - updateController(connector, controller); + updateController(controller); } - /** - * Removes an entry from the Group, potentially splitting it if needed. By calling this function, the caller asserts - * that this group contains the specified position; the function may misbehave if the group does not actually contain - * the specified position. - * - * @param pos The position of the entry to remove. - * @param split A consumer for the resulting fresh graphs from the split operation. - */ - public void removeAt(long pos, Consumer> split) { - + private void internalRemove(long pos, Consumer> split) { // The contains() check can be skipped here, because Graph will only call remove() if it knows that the group contains the entry. // For now, it is retained for completeness and debugging purposes. if (!contains(pos)) { @@ -371,34 +288,37 @@ public void removeAt(long pos, Consumer> split) { // If removing the entry would not cause a group split, then it is safe to remove the entry directly. if (isExternal(pos)) { if (removeNode(pos)) { + if (controller != null) { + try { + controller.change(); + } catch (Exception ex) { + Tesseract.LOGGER.warn("Error updating controller : " + ex); + } + } return; } int pairing = connectors.remove(pos); Grid grid = grids.get(pairing); - Cache cable = grid.getConnectors().get(pos); - resetControllerHost(cable); // No check is needed here, because the caller already asserts that the Group contains the specified position. // Thus, if this is not a node, then it is guaranteed to be a connector. grid.removeAt( - pos, - newGrid -> { - int newId = CID.nextId(); - grids.put(newId, newGrid); + pos, + newGrid -> { + int newId = CID.nextId(); + grids.put(newId, newGrid); - for (long move : newGrid.getConnectors().keySet()) { - connectors.put(move, newId); + for (long move : newGrid.getConnectors().keySet()) { + connectors.put(move, newId); + } } - } ); // Avoid leaving empty grids within the grid list. if (grid.countConnectors() == 0) { grids.remove(pairing); } - - return; } // If none of the fast routes work, we need to due a full group-traversal to figure out how the graph will be split. @@ -411,18 +331,18 @@ public void removeAt(long pos, Consumer> split) { List colored = new ObjectArrayList<>(); int bestColor = divider.divide( - removed -> removed.add(pos), - roots -> { - Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { - long side = position.offset(direction).asLong(); - - if (linked(pos, direction, side)) { - roots.add(side); + removed -> removed.add(pos), + roots -> { + Pos position = new Pos(pos); + for (Direction direction : Graph.DIRECTIONS) { + long side = position.offset(direction).asLong(); + + if (linked(pos, direction, side)) { + roots.add(side); + } } - } - }, - colored::add + }, + colored::add ); List> splitGrids = null; @@ -435,6 +355,7 @@ public void removeAt(long pos, Consumer> split) { for (long move : centerGrid.getConnectors().keySet()) { connectors.remove(move); + //nodes.remove(move); excluded.add(move); } @@ -447,7 +368,7 @@ public void removeAt(long pos, Consumer> split) { for (int i = 0; i < colored.size(); i++) { LongSet found = colored.get(i); - Group newGroup; + Group newGroup; if (i != bestColor) { newGroup = new Group<>(); @@ -487,7 +408,7 @@ public void removeAt(long pos, Consumer> split) { // Add the fragments of the center grid, if present, to each group if (splitGrids != null) { - Iterator> it = splitGrids.iterator(); + Iterator> it = splitGrids.iterator(); while (it.hasNext()) { Grid grid = it.next(); @@ -505,16 +426,31 @@ public void removeAt(long pos, Consumer> split) { if (i != bestColor) { if (controller != null) { newGroup.controller = controller.clone(newGroup); - newGroup.findNextValidHost(null); } split.accept(newGroup); - } else { - releaseControllerHost(); - findNextValidHost(null); + } else if (controller != null) { + try { + controller.change(); + } catch (Exception ex) { + Tesseract.LOGGER.warn("Error updating controller : " + ex); + } } } } + /** + * Removes an entry from the Group, potentially splitting it if needed. By calling this function, the caller asserts + * that this group contains the specified position; the function may misbehave if the group does not actually contain + * the specified position. + * + * @param pos The position of the entry to remove. + * @param split A consumer for the resulting fresh graphs from the split operation. + */ + public boolean removeAt(long pos, Consumer> split) { + internalRemove(pos, split); + return true; + } + /** * Removes the nodes from nearest grids and pairs. * @@ -522,16 +458,14 @@ public void removeAt(long pos, Consumer> split) { * @return True if were deleted, false otherwise. */ private boolean removeNode(long pos) { - Cache node = nodes.remove(pos); + NodeCache node = nodes.remove(pos); if (node == null) { return false; } - resetControllerHost(node); - // Clear removing node from nearest grid Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { + for (Direction direction : Graph.DIRECTIONS) { long side = position.offset(direction).asLong(); int id = connectors.get(side); @@ -539,32 +473,31 @@ private boolean removeNode(long pos) { grids.get(id).removeNode(pos); } } - return true; } /** * Adds a new grid to the group. * - * @param id The group id. + * @param id The group id. * @param grid The grid object. */ private void addGrid(int id, Grid grid) { grids.put(id, grid); - for (long moved : grid.getConnectors().keySet()) { - connectors.put(moved, id); + for (Long2ObjectMap.Entry> moved : grid.getConnectors().long2ObjectEntrySet()) { + connectors.put(moved.getLongKey(), id); } } /** * Gets near grid by a given position and direction value. * - * @param pos The position of the grid. + * @param pos The position of the grid. * @param direction The direction we are looking to. * @return The grid map, guaranteed to not be null. */ - public Grid getGridAt(long pos, Dir direction) { + public Grid getGridAt(long pos, Direction direction) { int id = connectors.get(pos); if (id != CID.INVALID) { @@ -572,11 +505,33 @@ public Grid getGridAt(long pos, Dir direction) { if (grid.connects(pos, direction.getOpposite())) { return grid; } + } else { + id = connectors.get(Pos.offset(pos, direction.getOpposite())); + if (id != CID.INVALID) { + return grids.get(id); + } } return null; } + public void getGroupInfo(long pos, List list) { + Grid grid = this.grids.get(connectors.get(pos)); + if (grid == null) return; + list.add(String.format("Connector count (grid): %d", grid.countConnectors())); + list.add(String.format("Node count (grid): %d", grid.countNodes())); + list.add(String.format("Connector count (group): %d", connectors.size())); + list.add(String.format("Node count (group): %d", nodes.size())); + } + + public Cache getConnector(long pos) { + int id = this.connectors.get(pos); + if (id != CID.INVALID) { + return this.grids.get(id).getConnectors().get(pos); + } + return null; + } + /** * Tests if a particular position is only connected to the group on a single side, or is the only entry in the group. * @@ -591,7 +546,7 @@ private boolean isExternal(long pos) { int neighbors = 0; Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { + for (Direction direction : Graph.DIRECTIONS) { long side = position.offset(direction).asLong(); if (contains(side)) { @@ -604,11 +559,11 @@ private boolean isExternal(long pos) { /** * Merges one group to the another. + * * @param other The another group. - * @param pos The given position. + * @param pos The given position. */ - public void mergeWith(Group other, long pos) { - other.releaseControllerHost(); + public void mergeWith(Group other, long pos) { nodes.putAll(other.nodes); connectors.putAll(other.connectors); @@ -624,7 +579,7 @@ public void mergeWith(Group other, long pos) { Grid currentGrid = grids.get(pairing); Pos position = new Pos(pos); - for (Dir direction : Dir.VALUES) { + for (Direction direction : Graph.DIRECTIONS) { long side = position.offset(direction).asLong(); if (!currentGrid.connects(pos, direction)) { @@ -655,4 +610,48 @@ public void mergeWith(Group other, long pos) { grids.putAll(other.grids); } + + /** + * Checks the health of this group, if there is any issue present. + */ + public void healthCheck() { + /*Long2IntMap count = new Long2IntOpenHashMap(); + + for (Int2ObjectMap.Entry> grids : this.grids.int2ObjectEntrySet()) { + Long2ObjectMap> grid = grids.getValue().getConnectors(); + for (Long2ObjectMap.Entry> connectors : grid.long2ObjectEntrySet()) { + BlockPos pos = BlockPos.fromLong(connectors.getLongKey()); + Cache cache = connectors.getValue(); + C value = cache.value(); + + byte cachedConn = cache.connectivity(); + for (int i = 0; i < Graph.DIRECTIONS.length; i++) { + boolean connects = value.connects(Graph.DIRECTIONS[i]); + boolean connectsCache = Connectivity.has(cachedConn, i); + if (connects != connectsCache) { + warn(pos); + } + if (connectsCache) { + count.compute(pos.offset(Graph.DIRECTIONS[i]).toLong(), (k, v) -> + v == null ? 1 : v + 1 + ); + + } + } + } + } + for (Long2ObjectMap.Entry> node : this.nodes.long2ObjectEntrySet()) { + NodeCache cache = node.getValue(); + if (cache.isPipe()) continue; + if (cache.count() != count.get(node.getLongKey())) { + warn(BlockPos.fromLong(node.getLongKey())); + Tesseract.LOGGER.error("Expected " + cache.count() + " connections but only got " + count.get(node.getLongKey())); + Tesseract.LOGGER.error("This is a bug, report to mod authors"); + } + }*/ + } + + private void warn(BlockPos pos) { + Tesseract.LOGGER.error("Caught invalid position in Tesseract at position: " + pos); + } } diff --git a/src/main/java/tesseract/graph/INode.java b/src/main/java/tesseract/graph/INode.java index d865610d..87844fa7 100644 --- a/src/main/java/tesseract/graph/INode.java +++ b/src/main/java/tesseract/graph/INode.java @@ -1,6 +1,6 @@ package tesseract.graph; -import tesseract.util.Dir; +import net.minecraft.util.Direction; /** * A simple interface for representing objects that contain groups of positions that are connected in various ways. @@ -8,27 +8,30 @@ */ public interface INode { - /** - * Tests whether this container contains the specified position. - * @param pos The position that the container may potentially contain. - * @return Whether the container contains the specified position. - */ - boolean contains(long pos); + /** + * Tests whether this container contains the specified position. + * + * @param pos The position that the container may potentially contain. + * @return Whether the container contains the specified position. + */ + boolean contains(long pos); - /** - * Tests whether adjacent positions are linked. - * @param from The starting position. - * @param towards The face on the starting position. - * @param to The target position, must be equal to from.offset(towards). - * @return Whether the positions are linked. If a position is not contained within this container, returns false. - */ - boolean linked(long from, Dir towards, long to); + /** + * Tests whether adjacent positions are linked. + * + * @param from The starting position. + * @param towards The face on the starting position. + * @param to The target position, must be equal to from.offset(towards). + * @return Whether the positions are linked. If a position is not contained within this container, returns false. + */ + boolean linked(long from, Direction towards, long to); - /** - * Tests whether the given position can link on the given side. - * @param pos The starting position, which must exist in the container. - * @param towards The face on the starting position. - * @return Whether the position would connect on the given side, returns false if the position is not within this container. - */ - boolean connects(long pos, Dir towards); + /** + * Tests whether the given position can link on the given side. + * + * @param pos The starting position, which must exist in the container. + * @param towards The face on the starting position. + * @return Whether the position would connect on the given side, returns false if the position is not within this container. + */ + boolean connects(long pos, Direction towards); } diff --git a/src/main/java/tesseract/graph/NodeCache.java b/src/main/java/tesseract/graph/NodeCache.java new file mode 100644 index 00000000..6b1607e7 --- /dev/null +++ b/src/main/java/tesseract/graph/NodeCache.java @@ -0,0 +1,93 @@ +package tesseract.graph; + +import java.util.EnumMap; +import net.minecraft.util.Direction; +import tesseract.Tesseract; +import tesseract.api.IConnectable; +import tesseract.graph.Graph.INodeGetter; + +import java.util.EnumMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; + +public class NodeCache implements IConnectable { + + private final EnumMap value; + public final INodeGetter getter; + private final BiPredicate validator; + private final BiConsumer callback; + private final long pos; + private final boolean pipe; + + /** + * Creates a cache instance. + */ + public NodeCache(long pos, INodeGetter getter, BiPredicate validator, BiConsumer callback) { + this.value = new EnumMap<>(Direction.class); + this.getter = getter; + this.pos = pos; + this.validator = validator; + this.callback = callback; + this.pipe = false; + for (Direction d : Graph.DIRECTIONS) { + updateSide(d); + } + } + + public NodeCache(long pos, INodeGetter getter) { + this.value = new EnumMap<>(Direction.class); + this.getter = getter; + this.pos = pos; + this.pipe = true; + this.validator = null; + this.callback = null; + for (Direction d : Graph.DIRECTIONS) { + updateSide(d); + } + } + + + public boolean connects(Direction side) { + return value.get(side) != null; + } + + public boolean updateSide(Direction side) { + if (!pipe && !validator.test(side, pos)) { + value.remove(side); + return false; + } + //if we have this key it means the capability is still valid. + if (this.value.containsKey(side)) return true; + T t = getter.get(pos, side, () -> callback.accept(side, pos)); + if (t == null) { + if (!pipe) Tesseract.LOGGER.info("NULL returned in NodeCache when not expected!"); + this.value.remove(side); + return false; + } + this.value.put(side, t); + return true; + } + + public boolean clearSide(Direction side) { + value.remove(side); + return count() > 0; + } + + public T value(Direction side) { + return value.get(side); + } + + public Iterable> values() { + return value.entrySet(); + } + + public int count() { + return value.size(); + } + + @Override + public boolean validate(Direction dir) { + return false; + } +} diff --git a/src/main/java/tesseract/graph/Path.java b/src/main/java/tesseract/graph/Path.java index 980bf682..4ec643ca 100644 --- a/src/main/java/tesseract/graph/Path.java +++ b/src/main/java/tesseract/graph/Path.java @@ -1,13 +1,16 @@ package tesseract.graph; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; + import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +import net.minecraft.util.Direction; import tesseract.api.IConnectable; import tesseract.util.Node; - -import java.util.Deque; -import java.util.Iterator; +import tesseract.util.Pos; /** * The Path is a class that should work with paths for grids. @@ -19,19 +22,19 @@ public class Path { private final Long2ObjectMap full = new Long2ObjectLinkedOpenHashMap<>(); private final Long2ObjectMap cross = new Long2ObjectLinkedOpenHashMap<>(); + /** * Creates a path instance. * * @param connectors The connectors array. - * @param path The path queue. + * @param path The path queue. */ protected Path(Long2ObjectMap> connectors, Deque path) { origin = path.pollLast(); target = path.pollFirst(); - - Iterator it = path.descendingIterator(); - while (it.hasNext()) { - Node node = it.next(); + Node node; + while (!path.isEmpty()) { + node = path.removeLast(); long pos = node.asLong(); Cache cache = connectors.get(pos); @@ -63,14 +66,14 @@ public Node target() { * @return Gets the full connectors path. */ public Long2ObjectMap getFull() { - return Long2ObjectMaps.unmodifiable(full); + return full; } /** * @return Gets the crossroad connectors path. */ public Long2ObjectMap getCross() { - return Long2ObjectMaps.unmodifiable(cross); + return cross; } /** diff --git a/src/main/java/tesseract/graph/TestBench.java b/src/main/java/tesseract/graph/TestBench.java index 98c3c9bf..1e777aa2 100644 --- a/src/main/java/tesseract/graph/TestBench.java +++ b/src/main/java/tesseract/graph/TestBench.java @@ -2,10 +2,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import net.minecraft.util.Direction; import tesseract.api.IConnectable; -import tesseract.api.ITickHost; -import tesseract.api.ITickingController; -import tesseract.util.Dir; import tesseract.util.Node; import tesseract.util.Pos; @@ -21,7 +19,7 @@ class TestBench { public static void main(String[] args) throws Exception { - Graph graph = new Graph<>(); + Graph graph = new Graph<>(() -> null); BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); while (true) { @@ -40,14 +38,23 @@ public static void main(String[] args) throws Exception { long position = pos.asLong(); if (points.length == 5 && points[4].startsWith("c")) { - if (!graph.addConnector(position, new Cache<>(new ExampleConnector()), null)) { + /*if (!graph.addConnector(position, new Cache<>(new ExampleConnector()), null)) { System.out.println("Error: connector at" + pos + " already exists in the graph"); continue; - } + }*/ } else { - if (!graph.addNode(position, new Cache<>(new ExampleNode()), null)) { - System.out.println("Error: node at" + pos + " already exists in the graph"); - continue; + for (Direction d : Graph.DIRECTIONS) { + long posC = Pos.offset(position, d); + Group group = graph.getGroupAt(posC); + if (group == null) + continue; + Cache val = group.getConnector(posC); + if (val != null) { + /*if (!graph.addNode(position, (a,b) -> new ExampleNode(), Pos.subToDir(posC, position), () -> null, true)) { + System.out.println("error"); + }*/ + } + } } System.out.println("Added " + pos + " to the graph"); @@ -61,7 +68,7 @@ public static void main(String[] args) throws Exception { Pos pos = new Pos(Integer.parseInt(points[1]), Integer.parseInt(points[2]), Integer.parseInt(points[3])); - graph.removeAt(pos.asLong()); + //graph.removeAt(pos.asLong()); System.out.println("Removed " + pos + " from the graph"); @@ -75,7 +82,8 @@ public static void main(String[] args) throws Exception { long origin = packAll(Integer.parseInt(points[1]), Integer.parseInt(points[2]), Integer.parseInt(points[3])); long target = packAll(Integer.parseInt(points[4]), Integer.parseInt(points[5]), Integer.parseInt(points[6])); - for (Int2ObjectMap.Entry> group : graph.getGroups().int2ObjectEntrySet()) { + for (Int2ObjectMap.Entry> group : graph.getGroups() + .int2ObjectEntrySet()) { for (Grid grid : group.getValue().getGrids().values()) { for (Node node : grid.getPath(origin, target)) { System.out.println(node); @@ -89,26 +97,29 @@ public static void main(String[] args) throws Exception { System.out.println("Graph contains " + graph.countGroups() + " groups:"); - for (Int2ObjectMap.Entry> group : graph.getGroups().int2ObjectEntrySet()) { - System.out.println(" Group " + group.getIntKey() + " contains " + group.getValue().countBlocks() + " blocks: "); + for (Int2ObjectMap.Entry> group : graph.getGroups() + .int2ObjectEntrySet()) { + System.out + .println(" Group " + group.getIntKey() + " contains " + group.getValue().countBlocks() + " blocks: "); - for (Long2ObjectMap.Entry> node : group.getValue().getNodes().long2ObjectEntrySet()) { - System.out.println(" Node at " + new Pos(node.getLongKey()) + ": " + node.getValue().value()); + for (Long2ObjectMap.Entry> node : group.getValue().getNodes().long2ObjectEntrySet()) { + //System.out.println(" Node at " + new Pos(node.getLongKey()) + ": " + node.getValue().value()); } for (Grid grid : group.getValue().getGrids().values()) { System.out.println(" Grid contains " + grid.countConnectors() + " connectors:"); for (Long2ObjectMap.Entry> connector : grid.getConnectors().long2ObjectEntrySet()) { - System.out.println(" Connector at " + new Pos(connector.getLongKey()) + ": " + connector.getValue().value()); + System.out + .println(" Connector at " + new Pos(connector.getLongKey()) + ": " + connector.getValue().value()); } int linked = grid.countNodes(); if (linked != 0) { System.out.println(" Grid contains " + linked + " linked nodes:"); - for (long pos : grid.getNodes().keySet()) { - System.out.println(" Node at " + new Pos(pos)); - } + // for (long pos : grid.getNodes()) { + // System.out.println(" Node at " + new Pos(pos)); + // } } } } @@ -125,26 +136,21 @@ public String toString() { } @Override - public boolean connects(Dir direction) { + public boolean connects(Direction direction) { return true; } - } - - private static class ExampleNode implements IConnectable, ITickHost { - - @Override - public String toString() { - return "ExampleNode"; - } @Override - public boolean connects(Dir direction) { + public boolean validate(Direction dir) { return true; } + } + + private static class ExampleNode { @Override - public void reset(ITickingController oldController, ITickingController newController) { - System.out.println("oldController: " + oldController + "| newController: " + newController); + public String toString() { + return "ExampleNode"; } } } diff --git a/src/main/java/tesseract/graph/traverse/ASFinder.java b/src/main/java/tesseract/graph/traverse/ASFinder.java index b3ccdf5e..c9c49b57 100644 --- a/src/main/java/tesseract/graph/traverse/ASFinder.java +++ b/src/main/java/tesseract/graph/traverse/ASFinder.java @@ -1,12 +1,16 @@ package tesseract.graph.traverse; -import it.unimi.dsi.fastutil.objects.*; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.util.Direction; +import tesseract.graph.Graph; import tesseract.graph.INode; -import tesseract.util.Dir; import tesseract.util.Node; import tesseract.util.Pos; -import java.util.*; +import java.util.ArrayDeque; +import java.util.ConcurrentModificationException; +import java.util.Deque; +import java.util.Set; /** * A Star Algorithm implementation for converting a graph – consisting of the grid – into a route through the grid. @@ -62,12 +66,10 @@ public Deque traverse(long origin, long target) { open.remove(current); closed.add(current); - for (Node n : getNeighboringNodes(current)) { - + for (Node n : getNeighboringNodes(current, origin, target)) { if (n == null) { break; } - if (closed.contains(n)) { continue; } @@ -124,10 +126,10 @@ public void retracePath(Node current) { public boolean retraceNode(Node current) { int connections = 0; - for (Dir direction : Dir.VALUES) { + for (Direction direction : Graph.DIRECTIONS) { long pos = current.offset(direction).asLong(); - if (container.connects(pos, direction)) { + if (container.connects(pos, direction.getOpposite())) { connections++; } } @@ -155,17 +157,17 @@ private Node getLowestF() { * Lookups for a set of neighbors of a given node. * * @param current The given node. + * @param end the target node. * @return The list of nodes. */ - public Node[] getNeighboringNodes(Node current) { - Node[] neighbors = new Node[6]; int i = 0; - - for (Dir direction : Dir.VALUES) { - Pos pos = current.offset(direction); - long side = pos.asLong(); - - if (container.contains(side)) { - neighbors[i++] = new Node(pos, direction.getOpposite()); + public Node[] getNeighboringNodes(Node current, long start, long end) { + Node[] neighbors = new Node[6]; + int i = 0; + + for (Direction direction : Graph.DIRECTIONS) { + long side = Pos.offset(current.asLong(), direction); + if (container.contains(side) && container.connects(side, direction.getOpposite())) { + neighbors[i++] = new Node(side, direction.getOpposite()); } } diff --git a/src/main/java/tesseract/graph/traverse/BFDivider.java b/src/main/java/tesseract/graph/traverse/BFDivider.java index d7a11117..94c32a4b 100644 --- a/src/main/java/tesseract/graph/traverse/BFDivider.java +++ b/src/main/java/tesseract/graph/traverse/BFDivider.java @@ -14,81 +14,81 @@ */ public class BFDivider { - private final BFSearcher searcher; - private final Long2IntOpenHashMap roots = new Long2IntOpenHashMap(); - private final LongLinkedOpenHashSet lookup = new LongLinkedOpenHashSet(); - - /** - * Creates a reusable BFDivider instance that will devides the provided container. - * - * @param container The container to use for devides operations. - */ - public BFDivider(INode container) { - searcher = new BFSearcher(container); - roots.defaultReturnValue(Integer.MAX_VALUE); - } - - /** - * Executes the divide operation with the given parameters. - * - * @param removed This function is called once, allowing the caller to provide a list of removed positions. When executing - * breadth first search operations, these positions will not be traversed, making it possible to truly - * remove them from the node contained being searched after divide is complete. - * @param rootProvider Like the previous parameter, this allows the caller to provide a list of positions. However, - * these positions will be used as positions to initiate the search operations from - usually, - * they will be the neighbors of all items in the removed set. - * @param split An acceptor of the sets of divided positions. Each set contains a set of positions determined to be - * connected by the node container. - * @return The index in the sequence of split position sets corresponding to the largest set of positions, ie. a - * return value of 0 indicates that the first returned set was the largest. - */ - public int divide(Consumer removed, Consumer rootProvider, Consumer split) { - if (!lookup.isEmpty() || !roots.isEmpty()) { - throw new ConcurrentModificationException("Attempted to run concurrent divide operations on the same BFDivider instance"); - } - - rootProvider.accept(lookup); - - int bestCount = 0; - int bestColor = 0; - int currentColor = 0; - - try { - for (long root : lookup) { - // Check if this root has already been colored. - int existingColor = roots.get(root); - - if (existingColor != roots.defaultReturnValue()) { - // Already colored! No point in doing it again. - continue; - } - - final int color = currentColor++; - roots.put(root, color); - - LongSet found = new LongLinkedOpenHashSet(); - - searcher.search(root, reached -> { - if (lookup.contains(reached)) { - roots.put(reached, color); - } - - found.add(reached); - }, removed); - - if (found.size() > bestCount) { - bestCount = found.size(); - bestColor = color; - } - - split.accept(found); - } - } finally { - // Clean up the open/closed sets - lookup.clear(); - roots.clear(); - } - - return bestColor; - } + private final BFSearcher searcher; + private final Long2IntOpenHashMap roots = new Long2IntOpenHashMap(); + private final LongLinkedOpenHashSet lookup = new LongLinkedOpenHashSet(); + + /** + * Creates a reusable BFDivider instance that will devides the provided container. + * + * @param container The container to use for devides operations. + */ + public BFDivider(INode container) { + searcher = new BFSearcher(container); + roots.defaultReturnValue(Integer.MAX_VALUE); + } + + /** + * Executes the divide operation with the given parameters. + * + * @param removed This function is called once, allowing the caller to provide a list of removed positions. When executing + * breadth first search operations, these positions will not be traversed, making it possible to truly + * remove them from the node contained being searched after divide is complete. + * @param rootProvider Like the previous parameter, this allows the caller to provide a list of positions. However, + * these positions will be used as positions to initiate the search operations from - usually, + * they will be the neighbors of all items in the removed set. + * @param split An acceptor of the sets of divided positions. Each set contains a set of positions determined to be + * connected by the node container. + * @return The index in the sequence of split position sets corresponding to the largest set of positions, ie. a + * return value of 0 indicates that the first returned set was the largest. + */ + public int divide(Consumer removed, Consumer rootProvider, Consumer split) { + if (!lookup.isEmpty() || !roots.isEmpty()) { + throw new ConcurrentModificationException("Attempted to run concurrent divide operations on the same BFDivider instance"); + } + + rootProvider.accept(lookup); + + int bestCount = 0; + int bestColor = 0; + int currentColor = 0; + + try { + for (long root : lookup) { + // Check if this root has already been colored. + int existingColor = roots.get(root); + + if (existingColor != roots.defaultReturnValue()) { + // Already colored! No point in doing it again. + continue; + } + + final int color = currentColor++; + roots.put(root, color); + + LongSet found = new LongLinkedOpenHashSet(); + + searcher.search(root, reached -> { + if (lookup.contains(reached)) { + roots.put(reached, color); + } + + found.add(reached); + }, removed); + + if (found.size() > bestCount) { + bestCount = found.size(); + bestColor = color; + } + + split.accept(found); + } + } finally { + // Clean up the open/closed sets + lookup.clear(); + roots.clear(); + } + + return bestColor; + } } diff --git a/src/main/java/tesseract/graph/traverse/BFSearcher.java b/src/main/java/tesseract/graph/traverse/BFSearcher.java index 60890bae..68728f2a 100644 --- a/src/main/java/tesseract/graph/traverse/BFSearcher.java +++ b/src/main/java/tesseract/graph/traverse/BFSearcher.java @@ -4,8 +4,9 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongPriorityQueue; import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.util.Direction; +import tesseract.graph.Graph; import tesseract.graph.INode; -import tesseract.util.Dir; import tesseract.util.Pos; import java.util.ConcurrentModificationException; @@ -41,8 +42,8 @@ public BFSearcher(INode container) { * to the provided consumer. As a result of the algorithm, each reported position is guaranteed to be connected to * an existing position, or in the case of the first reported position, it will be identical to from. * - * @param from The start position of the search operation. This will be the first position reported to the consumer. - * @param reached The receiver of the discovered positions + * @param from The start position of the search operation. This will be the first position reported to the consumer. + * @param reached The receiver of the discovered positions * @param excluder A function that can add values to the closed set prior to the search operation. * They will not be reported or traversed; null is interpreted to mean no exclusions. */ @@ -79,7 +80,7 @@ public void search(long from, LongConsumer reached, Consumer excluder) Pos position = new Pos(current); // Discover new nodes - for (Dir direction : Dir.VALUES) { + for (Direction direction : Graph.DIRECTIONS) { long pos = position.offset(direction).asLong(); if (closed.contains(pos)) { diff --git a/src/main/java/tesseract/util/Dir.java b/src/main/java/tesseract/util/Dir.java deleted file mode 100644 index c6d1e270..00000000 --- a/src/main/java/tesseract/util/Dir.java +++ /dev/null @@ -1,70 +0,0 @@ -package tesseract.util; - - -/** - * Direction enum - */ -public enum Dir { - - DOWN(0, 1, new Pos(0, -1, 0)), - UP(1, 0, new Pos(0, 1, 0)), - NORTH(2, 3, new Pos(0, 0, -1)), - SOUTH(3, 2, new Pos(0, 0, 1)), - WEST(4, 5, new Pos(-1, 0, 0)), - EAST(5, 4, new Pos(1, 0, 0)); - - public static final Dir[] VALUES; - private final int index, opposite; - private final Pos direction; - - static { - VALUES = values(); - } - - /** - * Create a direction instance. - * @param index The index of the direction. - * @param opposite The opposite index of the direction. - * @param direction The direction vector. - */ - Dir(int index, int opposite, Pos direction) { - this.index = index; - this.opposite = opposite; - this.direction = direction; - } - - /** - * @return Gets the index. - */ - public int getIndex() { - return index; - } - - /** - * @return Gets the X direction offset. - */ - public int getXOffset() { - return direction.getX(); - } - - /** - * @return Gets the Y direction offset. - */ - public int getYOffset() { - return direction.getY(); - } - - /** - * @return Gets the Z direction offset. - */ - public int getZOffset() { - return direction.getZ(); - } - - /** - * @return Return the inverted direction. - */ - public Dir getOpposite() { - return VALUES[opposite]; - } -} diff --git a/src/main/java/tesseract/util/Node.java b/src/main/java/tesseract/util/Node.java index 683e472e..349b0169 100644 --- a/src/main/java/tesseract/util/Node.java +++ b/src/main/java/tesseract/util/Node.java @@ -1,12 +1,14 @@ package tesseract.util; +import net.minecraft.util.Direction; + /** * The Node is a pretty straightforward class resembling regular nodes. */ public class Node extends Pos { private Node parent; - private Dir direction; + private Direction direction; private int cost, heuristic, function; private boolean valid; private boolean crossroad; @@ -14,10 +16,10 @@ public class Node extends Pos { /** * Creates a node instance. * - * @param pos The position to duplicate. + * @param pos The position to duplicate. * @param direction The direction to the parent. */ - public Node(Pos pos, Dir direction) { + public Node(Pos pos, Direction direction) { super(pos); setDirection(direction); } @@ -25,10 +27,10 @@ public Node(Pos pos, Dir direction) { /** * Creates a node instance. * - * @param value The compressed position. + * @param value The compressed position. * @param direction The direction to the parent. */ - public Node(long value, Dir direction) { + public Node(long value, Direction direction) { super(value); setDirection(direction); } @@ -53,6 +55,7 @@ public int getCost() { /** * Sets the cost. + * * @param cost The cost value. */ public void setCost(int cost) { @@ -68,6 +71,7 @@ public int getHeuristic() { /** * Sets the heuristic. + * * @param heuristic The heuristic value. */ public void setHeuristic(int heuristic) { @@ -83,6 +87,7 @@ public int getFunction() { /** * Sets the function. + * * @param function The function value. */ public void setFunction(int function) { @@ -98,6 +103,7 @@ public Node getParent() { /** * Sets the parent node. + * * @param parent The parent node. */ public void setParent(Node parent) { @@ -107,15 +113,16 @@ public void setParent(Node parent) { /** * @return Gets the direction to the parent node. */ - public Dir getDirection() { + public Direction getDirection() { return direction; } /** * Sets the direction to the parent node. + * * @param direction The direction. */ - public void setDirection(Dir direction) { + public void setDirection(Direction direction) { this.direction = direction; } @@ -128,6 +135,7 @@ public boolean isValid() { /** * Sets the valid state. + * * @param valid True or false. */ public void setValid(boolean valid) { @@ -143,6 +151,7 @@ public boolean isCrossroad() { /** * Sets the cross state. + * * @param crossroad True or false. */ public void setCrossroad(boolean crossroad) { diff --git a/src/main/java/tesseract/util/Pos.java b/src/main/java/tesseract/util/Pos.java index 5f3b8c1b..7bae8308 100644 --- a/src/main/java/tesseract/util/Pos.java +++ b/src/main/java/tesseract/util/Pos.java @@ -1,5 +1,8 @@ package tesseract.util; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; + /** * Position in world. */ @@ -9,14 +12,16 @@ public class Pos { protected int x, y, z; /** - * Though it looks like an array, this is really more like a mapping. Key (index of this array) is the upper 5 bits - * of the result of multiplying a 32-bit unsigned integer by the B(2, 5) De Bruijn sequence 0x077CB531. Value (value + * Though it looks like an array, this is really more like a mapping. Key (index + * of this array) is the upper 5 bits of the result of multiplying a 32-bit + * unsigned integer by the B(2, 5) De Bruijn sequence 0x077CB531. Value (value * stored in the array) is the unique index (from the right) of the leftmo */ - private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, + 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; /** - * Is the given value a power of two? (1, 2, 4, 8, 16, ...) + * Is the given value a power of two? (1, 2, 4, 8, 16, ...) */ private static boolean isPowerOfTwo(int value) { return value != 0 && (value & value - 1) == 0; @@ -36,18 +41,51 @@ private static int smallestEncompassingPowerOfTwo(int value) { } /** - * Uses a B(2, 5) De Bruijn sequence and a lookup table to efficiently calculate the log-base-two of the given value. - * Optimized for cases where the input value is a power-of-two. If the input value is not a power-of-two, then + * Uses a B(2, 5) De Bruijn sequence and a lookup table to efficiently calculate + * the log-base-two of the given value. Optimized for cases where the input + * value is a power-of-two. If the input value is not a power-of-two, then * subtract 1 from the return value. */ private static int log2DeBruijn(int value) { value = isPowerOfTwo(value) ? value : smallestEncompassingPowerOfTwo(value); - return MULTIPLY_DE_BRUIJN_BIT_POSITION[(int)((long)value * 125613361L >> 27) & 31]; + return MULTIPLY_DE_BRUIJN_BIT_POSITION[(int) ((long) value * 125613361L >> 27) & 31]; + } + + /** + * Allows you to offset a long directly without any wrappers. + * + * @param value long position. + * @param dir direction. + * @return a new long pos. + */ + public static long offset(long value, Direction dir) { + int x = unpackX(value) + dir.getStepX(); + int y = unpackY(value) + dir.getStepY(); + int z = unpackZ(value) + dir.getStepZ(); + return packAll(x, y, z); + } + + public static long sub(long value, long other) { + int x = unpackX(value) - unpackX(other); + int y = unpackY(value) - unpackY(other); + int z = unpackZ(value) - unpackZ(other); + return packAll(x, y, z); + } + + // Returns a direction from value -> other. + public static Direction subToDir(long value, long other) { + long direction = sub(value, other); + return Direction.fromNormal(unpackX(direction), unpackY(direction), unpackZ(direction)); + } + + public static Direction blockPosToDir(BlockPos value, BlockPos other) { + return Direction.fromNormal(value.getX() - other.getX(), value.getY() - other.getY(), value.getZ() - other.getZ()); } /** - * Efficiently calculates the floor of the base-2 log of an integer value. This is effectively the index of the - * highest bit that is set. For example, if the number in binary is 0...100101, this will return 5. + * Efficiently calculates the floor of the base-2 log of an integer value. This + * is effectively the index of the highest bit that is set. For example, if the + * number in binary is 0...100101, this will return 5. */ private static int log2(int value) { return log2DeBruijn(value) - (isPowerOfTwo(value) ? 0 : 1); @@ -218,19 +256,19 @@ public long asLong() { * @param dir The moving direction. * @return The new instance of object. */ - public Pos offset(Dir dir) { - return new Pos(x + dir.getXOffset(), y + dir.getYOffset(), z + dir.getZOffset()); + public Pos offset(Direction dir) { + return new Pos(x + dir.getStepX(), y + dir.getStepY(), z + dir.getStepZ()); } /** * Moves the position in the provided direction. * * @param dir The moving direction. - * @param n The moving distance. + * @param n The moving distance. * @return The new instance of object. */ - public Pos offset(Dir dir, int n) { - return n == 0 ? this : new Pos(x + dir.getXOffset() * n, y + dir.getYOffset() * n, z + dir.getZOffset() * n); + public Pos offset(Direction dir, int n) { + return n == 0 ? this : new Pos(x + dir.getStepX() * n, y + dir.getStepY() * n, z + dir.getStepZ() * n); } /** @@ -240,7 +278,7 @@ public Pos offset(Dir dir, int n) { * @return x coordinate. */ public static int unpackX(long value) { - return (int)(value << 64 - X_FIELD - NUM_X_BITS >> 64 - NUM_X_BITS); + return (int) (value << 64 - X_FIELD - NUM_X_BITS >> 64 - NUM_X_BITS); } /** @@ -250,7 +288,7 @@ public static int unpackX(long value) { * @return y coordinate. */ public static int unpackY(long value) { - return (int)(value << 64 - NUM_Y_BITS >> 64 - NUM_Y_BITS); + return (int) (value << 64 - NUM_Y_BITS >> 64 - NUM_Y_BITS); } /** @@ -260,7 +298,7 @@ public static int unpackY(long value) { * @return z coordinate. */ public static int unpackZ(long value) { - return (int)(value << 64 - Z_FIELD - NUM_Z_BITS >> 64 - NUM_Z_BITS); + return (int) (value << 64 - Z_FIELD - NUM_Z_BITS >> 64 - NUM_Z_BITS); } /** @@ -291,7 +329,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return (int)(value ^ value >>> 32); + return (int) (value ^ value >>> 32); } @Override diff --git a/src/main/java/tesseract/util/SetUtil.java b/src/main/java/tesseract/util/SetUtil.java new file mode 100644 index 00000000..d154515d --- /dev/null +++ b/src/main/java/tesseract/util/SetUtil.java @@ -0,0 +1,35 @@ +package tesseract.util; + +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; + +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.LongPredicate; +import java.util.function.Predicate; + +public class SetUtil { + + public static Set union(Set first, Set second) { + Set set = new ObjectOpenHashSet<>(first); + set.addAll(second); + return set; + } + + public static Set union(Set first, Set second, Predicate keep) { + Set set = new ObjectOpenHashSet<>(first); + second.forEach(t -> { + if (keep.test(t)) set.add(t); + }); + return set; + } + + public static LongSet union(LongSet first, LongSet second, LongPredicate keep) { + LongSet set = new LongOpenHashSet(first); + second.forEach((Consumer) t -> { + if (keep.test(t)) set.add(t); + }); + return set; + } +} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 6de2eda2..b25b7446 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -2,16 +2,17 @@ modLoader="javafml" #loaderVersion="[${loader_version},)" loaderVersion="[31,)" issueTrackerURL="https://github.com/GregTech-Intergalactical/TesseractAPI/issues" +license="LGPL-v3" [[mods]] -modId="tesseract" -#version="${version}" -version="0.0.1" +modId="tesseractapi" +version="${file.jarVersion}" displayName="TesseractAPI" #updateJSONURL="http://myurl.me/" displayURL="https://github.com/GregTech-Intergalactical/TesseractAPI" #logoFile="examplemod.png" authors="coderbot16, qubka, repolainen, mitchej123, Muramasa" +credits="Rongmario - cheerleading from the side" description='''Powerful framework for energy, item, and fluid transport''' [[dependencies.tesseract]] modId="forge" diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 4fda3257..539e60bc 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,27 +1,27 @@ { "schemaVersion": 1, - "id": "tesseract", + "id": "tesseractapi", "version": "${version}", - "name": "Tesseract API", "description": "Powerful framework for energy, item, and fluid transport", "authors": [ - "coderbot16", "qubka", "repolainen", "mitchej123", "Muramasa" + "coderbot16", + "qubka", + "repolainen", + "mitchej123", + "Muramasa" ], "contact": { "sources": "https://github.com/GregTech-Intergalactical/TesseractAPI" }, - "license": "LGPL-3.0-only", "icon": "assets/tesseract/icon.png", - "environment": "*", "entrypoints": { "main": [ "tesseract.Tesseract" ] }, - "depends": { "fabricloader": ">=0.4.0", "fabric": "*" diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index 4e4ba4d0..9a327cf1 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -1,13 +1,13 @@ [ { - "modid": "tesseract", + "modid": "tesseractapi", "name": "Tesseract API", "description": "Powerful framework for energy, item, and fluid transport", "version": "${version}", "mcversion": "${mcversion}", "url": "", "updateUrl": "", - "authorList": ["coderbot16", "qubka", "repolainen", "mitchej123", "Muramasa"], + "authorList": ["coderbot16", "qubka", "repolainen", "mitchej123", "Muramasa", "Vliro"], "credits": "", "logoFile": "", "screenshots": [], diff --git a/src/test/java/tesseract/graph/GraphTest.java b/src/test/java/tesseract/graph/GraphTest.java index e3a9a536..1cc0ac4a 100644 --- a/src/test/java/tesseract/graph/GraphTest.java +++ b/src/test/java/tesseract/graph/GraphTest.java @@ -1,167 +1,208 @@ package tesseract.graph; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static tesseract.util.Pos.packAll; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; + +import com.google.common.primitives.Longs; + import org.junit.Test; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.util.Direction; import tesseract.api.IConnectable; -import tesseract.util.Dir; +import tesseract.graph.Graph.INodeGetter; import tesseract.util.Node; import tesseract.util.Pos; -import java.util.*; - -import static tesseract.util.Pos.packAll; -import static org.junit.Assert.*; - public class GraphTest { - @Test - public void system() { - Graph graph = new Graph<>(); - graph.addConnector(packAll(1, 0, 0), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(0, 1, 0), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(0, 2, 0), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(0, 3, 0), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(0, 4, 0), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(0, 5, 0), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(0, 6, 0), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(0, 0, 1), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(0, 0, -1), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(0, -1, 0), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(-1, 0, 0), new Cache<>(new TestConnector()), null); - assertEquals(6, graph.countGroups()); - graph.addConnector(packAll(0, 0, 0), new Cache<>(new TestConnector()), null); - assertEquals(1, graph.countGroups()); - graph.removeAt(packAll(0, 0, 0)); - assertEquals(6, graph.countGroups()); - graph.removeAt(packAll(0, 4, 0)); - assertEquals(7, graph.countGroups()); - graph.addConnector(packAll(0, 0, 0), new Cache<>(new TestConnector()), null); - graph.addConnector(packAll(0, 4, 0), new Cache<>(new TestConnector()), null); - assertEquals(1, graph.countGroups()); - Deque set1 = new ArrayDeque<>(); - for (Group group : graph.getGroups().values()) { - for (Grid grid : group.getGrids().values()) { - set1 = grid.getPath(packAll(0, -1, 0), packAll(0, 6, 0)); - } - } - List set2 = new ObjectArrayList<>(); - set2.add(new Pos(0, -1, 0)); - set2.add(new Pos(0, 0, 0)); - set2.add(new Pos(0, 1, 0)); - set2.add(new Pos(0, 2, 0)); - set2.add(new Pos(0, 3, 0)); - set2.add(new Pos(0, 4, 0)); - set2.add(new Pos(0, 5, 0)); - set2.add(new Pos(0, 6, 0)); - Iterator it = set1.descendingIterator(); - for (Pos pos : set2) { - assertEquals(pos, it.next()); - } - } - - @Test - public void contains() { - Graph graph = new Graph<>(); - long pos = packAll(1, 1, 1); - assertFalse(graph.contains(pos)); - graph.addNode(pos, new Cache<>(new TestNode()), null); - assertTrue(graph.contains(pos)); - } - - @Test - public void linked() { - Graph graph = new Graph<>(); - long pos1 = packAll(0, 0, 0); - long pos2 = packAll(0, 1, 0); - graph.addNode(pos1, new Cache<>(new TestNode()), null); - graph.addNode(pos2, new Cache<>(new TestNode()), null); - assertTrue(graph.linked(pos1, null, pos2)); + /*@Test + public void system() { + Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); + INodeGetter tester = getter(map); + Graph graph = new Graph<>(); + graph.addConnector(packAll(1, 0, 0), new Cache<>(new TestConnector(packAll(1, 0, 0), map)), () -> null, tester, true); + graph.addConnector(packAll(0, 1, 0), new Cache<>(new TestConnector(packAll(0, 1, 0), map)), () -> null, tester, true); + graph.addConnector(packAll(0, 2, 0), new Cache<>(new TestConnector(packAll(0, 2, 0), map)), () -> null, tester, true); + graph.addConnector(packAll(0, 3, 0), new Cache<>(new TestConnector(packAll(0, 3, 0), map)), () -> null, tester, true); + graph.addConnector(packAll(0, 4, 0), new Cache<>(new TestConnector(packAll(0, 4, 0), map)), () -> null, tester, true); + graph.addConnector(packAll(0, 5, 0), new Cache<>(new TestConnector(packAll(0, 5, 0), map)), () -> null, tester, true); + graph.addConnector(packAll(0, 6, 0), new Cache<>(new TestConnector(packAll(0, 6, 0), map)), () -> null, tester, true); + graph.addConnector(packAll(0, 0, 1), new Cache<>(new TestConnector(packAll(0, 0, 1), map)), () -> null, tester, true); + graph.addConnector(packAll(0, 0, -1), new Cache<>(new TestConnector(packAll(0, 0, -1), map)), () -> null, tester, true); + graph.addConnector(packAll(0, -1, 0), new Cache<>(new TestConnector(packAll(0, -1, 0), map)), () -> null, tester, true); + graph.addConnector(packAll(-1, 0, 0), new Cache<>(new TestConnector(packAll(-1, 0, 0), map)), () -> null, tester, true); + assertEquals(6, graph.countGroups()); + graph.addConnector(packAll(0, 0, 0), new Cache<>(new TestConnector(packAll(0, 0, 0), map)), () -> null, tester, true); + assertEquals(1, graph.countGroups()); + //graph.removeAt(packAll(0, 0, 0)); + assertEquals(6, graph.countGroups()); + //graph.removeAt(packAll(0, 4, 0)); + assertEquals(7, graph.countGroups()); + graph.addConnector(packAll(0, 0, 0), new Cache<>(new TestConnector(packAll(0, 0, 0), map)), () -> null, tester, true); + graph.addConnector(packAll(0, 4, 0), new Cache<>(new TestConnector(packAll(0, 4, 0), map)), () -> null, tester, true); + + assertEquals(1, graph.countGroups()); + Deque set1 = new ArrayDeque<>(); + for (Group group : graph.getGroups().values()) { + for (Grid grid : group.getGrids().values()) { + set1 = grid.getPath(packAll(0, -1, 0), packAll(0, 6, 0)); + } } - - @Test - public void connects() { - Graph graph = new Graph<>(); - long pos = packAll(0, 0, 0); - graph.addNode(pos, new Cache<>(new TestNode()), null); - assertTrue(graph.connects(pos, null)); + List set2 = new ObjectArrayList<>(); + set2.add(new Pos(0, -1, 0)); + set2.add(new Pos(0, 0, 0)); + set2.add(new Pos(0, 1, 0)); + set2.add(new Pos(0, 2, 0)); + set2.add(new Pos(0, 3, 0)); + set2.add(new Pos(0, 4, 0)); + set2.add(new Pos(0, 5, 0)); + set2.add(new Pos(0, 6, 0)); + Iterator it = set1.descendingIterator(); + for (Pos pos : set2) { + assertEquals(pos, it.next()); } - - @Test - public void visit() { - Graph graph = new Graph<>(); - graph.addNode(packAll(5, 5, 5), new Cache<>(new TestNode()), null); - for (Group group : graph.getGroups().values()) { - assertEquals(1, group.countBlocks()); - } + } + + @Test + public void contains() { + Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); + INodeGetter tester = getter(map); + Graph graph = new Graph<>(); + long pos = packAll(1, 1, 1); + long posC = packAll(0, 1, 1); + assertFalse(graph.contains(pos)); + assertFalse(graph.contains(posC)); + graph.addConnector(pos, new Cache<>(new TestConnector(pos, map)), () -> null, tester, true); + graph.addConnector(posC, new Cache<>(new TestConnector(posC, map)), () -> null, tester, true); + + assertTrue(graph.contains(posC)); + //graph.addNode(pos, (a,p) -> new TestNode(), Pos.subToDir(posC, pos), () -> null, true); + assertTrue(graph.contains(pos)); + } + + @Test + public void linked() { + Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); + INodeGetter tester = getter(map); + Graph graph = new Graph<>(); + long pos = packAll(0,0,0); + long posC = packAll(0, 1, 0); + map.put(posC, new TestNode()); + + graph.addConnector(pos, new Cache<>(new TestConnector(pos, map)), () -> null, tester, true); + + assertTrue(graph.linked(pos, null, posC)); + } + + @Test + public void connects() { + Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); + INodeGetter tester = getter(map); + Graph graph = new Graph<>(); + long pos = packAll(0,0,0); + long posC = packAll(0, 1, 0); + map.put(posC, new TestNode()); + // graph.addNode(pos, new Cache<>(new TestNode()), null); + graph.addConnector(pos, new Cache<>(new TestConnector(pos, map)), () -> null, tester, true); + assertTrue(graph.connects(pos, null)); + } + + @Test + public void visit() { + Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); + INodeGetter tester = getter(map); + Graph graph = new Graph<>(); + graph.addConnector(packAll(0,0,0), new Cache<>(new TestConnector(packAll(0,0,0), map)), () -> null, tester, true); + for (Group group : graph.getGroups().values()) { + assertEquals(1, group.countBlocks()); } - - @Test - public void countGroups() { - Graph graph = new Graph<>(); - graph.addNode(packAll(0, 0, 0), new Cache<>(new TestNode()), null); - graph.addNode(packAll(1, 1, 1), new Cache<>(new TestNode()), null); - graph.addNode(packAll(2, 2, 2), new Cache<>(new TestNode()), null); - assertEquals(3, graph.countGroups()); + } + + @Test + public void countGroups() { + Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); + INodeGetter tester = getter(map); + Graph graph = new Graph<>(); + graph.addConnector(packAll(0, 0, 0), new Cache<>(new TestConnector(packAll(0, 0, 0), map)), () -> null, tester, true); + graph.addConnector(packAll(1, 1, 1), new Cache<>(new TestConnector(packAll(1, 1, 1), map)), () -> null, tester, true); + graph.addConnector(packAll(2, 2, 2), new Cache<>(new TestConnector(packAll(2, 2, 2), map)), () -> null, tester, true); + + assertEquals(3, graph.countGroups()); + } + + // @Test + // public void addConnector() { + // Graph graph = new Graph<>(); + // long pos = packAll(2, 2, 2); + // graph.addConnector(pos, new Cache<>(new TestConnector()), null); + // for (Group group : graph.getGroups().values()) { + // for (Grid grid : group.getGrids().values()) { + // for (long position : grid.getConnectors().keySet()) { + // assertEquals(position, pos); + // } + // } + // } + // } + + @Test + public void remove() { + Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); + INodeGetter tester = getter(map); + Graph graph = new Graph<>(); + long pos = packAll(0, 0, 0); + graph.addConnector(packAll(0, 0, 0), new Cache<>(new TestConnector(packAll(0, 0, 0), map)), () -> null, tester, true); + + assertEquals(1, graph.countGroups()); + //graph.removeAt(pos); + assertEquals(0, graph.countGroups()); + } + + protected static INodeGetter getter(Long2ObjectMap map) { + return (a,b,c) -> { + return map.get(a); + }; + } + + public static class TestConnector implements IConnectable { + + private final Long2ObjectMap nodes; + private final long pos; + + public TestConnector(long pos, Long2ObjectMap nodes) { + this.nodes = nodes; + this.pos = pos; } - @Test - public void addNode() { - Graph graph = new Graph<>(); - long pos = packAll(5, 5, 5); - graph.addNode(pos, new Cache<>(new TestNode()), null); - for (Group group : graph.getGroups().values()) { - for (long position : group.getNodes().keySet()) { - assertEquals(position, pos); - } - } + @Override + public String toString() { + return "TestCable"; } - @Test - public void addConnector() { - Graph graph = new Graph<>(); - long pos = packAll(2, 2, 2); - graph.addConnector(pos, new Cache<>(new TestConnector()), null); - for (Group group : graph.getGroups().values()) { - for (Grid grid : group.getGrids().values()) { - for (long position : grid.getConnectors().keySet()) { - assertEquals(position, pos); - } - } - } + @Override + public boolean connects(Direction direction) { + return true; } - @Test - public void remove() { - Graph graph = new Graph<>(); - long pos = packAll(0, 0, 0); - graph.addNode(pos, new Cache<>(new TestNode()), null); - assertEquals(1, graph.countGroups()); - graph.removeAt(pos); - assertEquals(0, graph.countGroups()); + @Override + public boolean validate(Direction dir) { + return nodes.containsKey(Pos.offset(this.pos, dir)); } + } - public static class TestConnector implements IConnectable { - - @Override - public String toString() { - return "TestCable"; - } - - @Override - public boolean connects(Dir direction) { - return true; - } - } - - private static class TestNode implements IConnectable { - - @Override - public String toString() { - return "TestNode"; - } + private static class TestNode { - @Override - public boolean connects(Dir direction) { - return true; - } + @Override + public String toString() { + return "TestNode"; } -} \ No newline at end of file + }*/ +}