[WIP] start 1.15 port
reorganize packages to match Fabric version use util classes from Fabric version
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package mods.octarinecore.client.resource
|
||||
|
||||
import mods.betterfoliage.resource.Identifier
|
||||
import mods.betterfoliage.resource.Sprite
|
||||
import mods.betterfoliage.util.map
|
||||
import net.minecraft.client.renderer.texture.AtlasTexture
|
||||
import net.minecraft.client.renderer.texture.MissingTextureSprite
|
||||
import net.minecraft.profiler.IProfiler
|
||||
import net.minecraft.resources.IResourceManager
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* Main entry point to atlas manipulation. Called from mixins that wrap [AtlasTexture.stitch] calls.
|
||||
*
|
||||
* 1. All registered providers receive an [AsyncSpriteProvider.setup] call. Providers can set up their
|
||||
* processing chain at this point, but should not do anything yet except configuration and housekeeping.
|
||||
* 2. The [CompletableFuture] of the stitch source finishes, starting the "discovery" phase. Providers
|
||||
* may register sprites in the [AtlasFuture].
|
||||
* 3. After all providers finish their discovery, the atlas is stitched.
|
||||
* 4. The [AtlasFuture] finishes, starting the "cleanup" phase. All [AtlasFuture.runAfter] and
|
||||
* [AtlasFuture.mapAfter] tasks are processed.
|
||||
* 5. After all providers finish their cleanup, we return to the original code path.
|
||||
*/
|
||||
class AsnycSpriteProviderManager<SOURCE: Any>(val profilerSection: String) {
|
||||
|
||||
val providers = mutableListOf<AsyncSpriteProvider<SOURCE>>()
|
||||
|
||||
/**
|
||||
* Needed in order to keep the actual [AtlasTexture.stitch] call in the original method, in case
|
||||
* other modders want to modify it too.
|
||||
*/
|
||||
class StitchWrapper(val idList: Iterable<Identifier>, val onComplete: (AtlasTexture.SheetData)->Unit) {
|
||||
fun complete(sheet: AtlasTexture.SheetData) = onComplete(sheet)
|
||||
}
|
||||
|
||||
var currentAtlas: AtlasFuture? = null
|
||||
var currentPhases: List<StitchPhases> = emptyList()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun prepare(sourceObj: Any, manager: IResourceManager, idList: Iterable<Identifier>, profiler: IProfiler): Set<Identifier> {
|
||||
profiler.startSection(profilerSection)
|
||||
|
||||
val source = CompletableFuture<SOURCE>()
|
||||
currentAtlas = AtlasFuture(idList)
|
||||
|
||||
currentPhases = providers.map { it.setup(manager, source, currentAtlas!!) }
|
||||
source.complete(sourceObj as SOURCE)
|
||||
currentPhases.forEach { it.discovery.get() }
|
||||
|
||||
return currentAtlas!!.idSet
|
||||
}
|
||||
|
||||
fun finish(sheetData: AtlasTexture.SheetData, profiler: IProfiler): AtlasTexture.SheetData {
|
||||
currentAtlas!!.complete(sheetData)
|
||||
currentPhases.forEach { it.cleanup.get() }
|
||||
currentAtlas = null
|
||||
currentPhases = emptyList()
|
||||
profiler.endSection()
|
||||
return sheetData
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a way for [AsyncSpriteProvider]s to register sprites to receive [CompletableFuture]s.
|
||||
* Tracks sprite ids that need to be stitched.
|
||||
*/
|
||||
class AtlasFuture(initial: Iterable<Identifier>) {
|
||||
val idSet = Collections.synchronizedSet(mutableSetOf<Identifier>().apply { addAll(initial) })
|
||||
protected val sheet = CompletableFuture<AtlasTexture.SheetData>()
|
||||
protected val finished = CompletableFuture<Void>()
|
||||
|
||||
fun complete(sheetData: AtlasTexture.SheetData) {
|
||||
sheet.complete(sheetData)
|
||||
finished.complete(null)
|
||||
}
|
||||
|
||||
fun sprite(id: String) = sprite(Identifier(id))
|
||||
fun sprite(id: Identifier): CompletableFuture<Sprite> {
|
||||
idSet.add(id)
|
||||
return sheet.map { sheetData -> sheetData.sprites.find { it.name == id } ?: throw IllegalStateException("Atlas does not contain $id") }
|
||||
}
|
||||
val missing = sheet.map { sheetData -> sheetData.sprites.find { it.name == MissingTextureSprite.getLocation() } }
|
||||
fun <T> mapAfter(supplier: ()->T): CompletableFuture<T> = finished.map{ supplier() }
|
||||
fun runAfter(action: ()->Unit): CompletableFuture<Void> = finished.thenRun(action)
|
||||
}
|
||||
|
||||
class StitchPhases(
|
||||
val discovery: CompletableFuture<Void>,
|
||||
val cleanup: CompletableFuture<Void>
|
||||
)
|
||||
|
||||
interface AsyncSpriteProvider<SOURCE: Any> {
|
||||
fun setup(manager: IResourceManager, source: CompletableFuture<SOURCE>, atlas: AtlasFuture): StitchPhases
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package mods.betterfoliage.resource.discovery
|
||||
|
||||
import com.google.common.base.Joiner
|
||||
import mods.betterfoliage.render.old.BlockCtx
|
||||
import mods.octarinecore.client.resource.AsyncSpriteProvider
|
||||
import mods.octarinecore.client.resource.AtlasFuture
|
||||
import mods.octarinecore.client.resource.StitchPhases
|
||||
import mods.betterfoliage.util.Int3
|
||||
import mods.betterfoliage.config.IBlockMatcher
|
||||
import mods.betterfoliage.config.ModelTextureList
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import mods.betterfoliage.util.findFirst
|
||||
import mods.betterfoliage.util.plus
|
||||
import mods.betterfoliage.util.sinkAsync
|
||||
import mods.betterfoliage.util.stripStart
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.renderer.BlockModelShapes
|
||||
import net.minecraft.client.renderer.model.BlockModel
|
||||
import net.minecraft.client.renderer.model.IUnbakedModel
|
||||
import net.minecraft.client.renderer.model.ModelBakery
|
||||
import net.minecraft.client.renderer.model.ModelResourceLocation
|
||||
import net.minecraft.client.renderer.model.VariantList
|
||||
import net.minecraft.client.renderer.texture.MissingTextureSprite
|
||||
import net.minecraft.resources.IResourceManager
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.IBlockReader
|
||||
import net.minecraftforge.registries.ForgeRegistries
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
interface ModelRenderRegistry<T> {
|
||||
operator fun get(ctx: BlockCtx) = get(ctx.state, ctx.world, ctx.pos)
|
||||
operator fun get(ctx: BlockCtx, offset: Int3) = get(ctx.state(offset), ctx.world, ctx.pos + offset)
|
||||
operator fun get(state: BlockState, world: IBlockReader, pos: BlockPos): T?
|
||||
}
|
||||
|
||||
abstract class ModelRenderRegistryRoot<T> : ModelRenderRegistry<T> {
|
||||
val registries = mutableListOf<ModelRenderRegistry<T>>()
|
||||
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = registries.findFirst { it[state, world, pos] }
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a single BlockState and all the IUnbakedModel it could render as.
|
||||
*/
|
||||
class ModelDiscoveryContext(
|
||||
bakery: ModelBakery,
|
||||
val state: BlockState,
|
||||
val modelId: ModelResourceLocation
|
||||
) {
|
||||
val models = bakery.unwrapVariants(bakery.getUnbakedModel(modelId) to modelId)
|
||||
.filter { it.second != bakery.getUnbakedModel(ModelBakery.MODEL_MISSING) }
|
||||
|
||||
fun ModelBakery.unwrapVariants(modelAndLoc: Pair<IUnbakedModel, ResourceLocation>): List<Pair<IUnbakedModel, ResourceLocation>> = when(val model = modelAndLoc.first) {
|
||||
is VariantList -> model.variantList.flatMap { variant -> unwrapVariants(getUnbakedModel(variant.modelLocation) to variant.modelLocation) }
|
||||
is BlockModel -> listOf(modelAndLoc)
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ModelDiscovery<T> : HasLogger, AsyncSpriteProvider<ModelBakery>, ModelRenderRegistry<T> {
|
||||
|
||||
var modelData: Map<BlockState, T> = emptyMap()
|
||||
protected set
|
||||
|
||||
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = modelData[state]
|
||||
|
||||
abstract fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<T>?
|
||||
|
||||
override fun setup(manager: IResourceManager, bakeryF: CompletableFuture<ModelBakery>, atlas: AtlasFuture): StitchPhases {
|
||||
val modelDataTemp = mutableMapOf<BlockState, CompletableFuture<T>>()
|
||||
|
||||
return StitchPhases(
|
||||
discovery = bakeryF.sinkAsync { bakery ->
|
||||
var errors = 0
|
||||
bakery.iterateModels { ctx ->
|
||||
try {
|
||||
processModel(ctx, atlas)?.let { modelDataTemp[ctx.state] = it }
|
||||
} catch (e: Exception) {
|
||||
errors++
|
||||
}
|
||||
}
|
||||
log("${modelDataTemp.size} BlockStates discovered, $errors errors")
|
||||
},
|
||||
cleanup = atlas.runAfter {
|
||||
modelData = modelDataTemp.filterValues { !it.isCompletedExceptionally }.mapValues { it.value.get() }
|
||||
val errors = modelDataTemp.values.filter { it.isCompletedExceptionally }.size
|
||||
log("${modelData.size} BlockStates loaded, $errors errors")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun ModelBakery.iterateModels(func: (ModelDiscoveryContext)->Unit) {
|
||||
ForgeRegistries.BLOCKS.flatMap { block ->
|
||||
block.stateContainer.validStates.map { state -> state to BlockModelShapes.getModelLocation(state) }
|
||||
}.forEach { (state, stateModelResource) ->
|
||||
func(ModelDiscoveryContext(this, state, stateModelResource))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ConfigurableModelDiscovery<T> : ModelDiscovery<T>() {
|
||||
|
||||
abstract val matchClasses: IBlockMatcher
|
||||
abstract val modelTextures: List<ModelTextureList>
|
||||
|
||||
abstract fun processModel(state: BlockState, textures: List<ResourceLocation>, atlas: AtlasFuture): CompletableFuture<T>?
|
||||
|
||||
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<T>? {
|
||||
val matchClass = matchClasses.matchingClass(ctx.state.block) ?: return null
|
||||
log("block state ${ctx.state}")
|
||||
log(" class ${ctx.state.block.javaClass.name} matches ${matchClass.name}")
|
||||
|
||||
if (ctx.models.isEmpty()) {
|
||||
log(" no models found")
|
||||
return null
|
||||
}
|
||||
|
||||
ctx.models.filter { it.first is BlockModel }.forEach { (model, location) ->
|
||||
model as BlockModel
|
||||
val modelMatch = modelTextures.firstOrNull { (model to location).derivesFrom(it.modelLocation) }
|
||||
if (modelMatch != null) {
|
||||
log(" model ${model} matches ${modelMatch.modelLocation}")
|
||||
|
||||
val textures = modelMatch.textureNames.map { it to model.resolveTextureName(it).textureLocation }
|
||||
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
|
||||
log(" sprites [$texMapString]")
|
||||
|
||||
if (textures.all { it.second != MissingTextureSprite.getLocation() }) {
|
||||
// found a valid model (all required textures exist)
|
||||
return processModel(ctx.state, textures.map { it.second}, atlas)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun Pair<BlockModel, ResourceLocation>.derivesFrom(targetLocation: ResourceLocation): Boolean {
|
||||
if (second.stripStart("models/") == targetLocation) return true
|
||||
if (first.parent != null && first.parentLocation != null)
|
||||
return Pair(first.parent!!, first.parentLocation!!).derivesFrom(targetLocation)
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user