Merge branch 'main' into 'main'

Berry Growth Performance Optimization

See merge request cable-mc/cobblemon!2142
This commit is contained in:
Polymeta
2026-02-26 12:15:32 +00:00
2 changed files with 56 additions and 11 deletions
@@ -81,7 +81,32 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
override fun isBonemealSuccess(world: Level, random: RandomSource, pos: BlockPos, state: BlockState) = !this.isMaxAge(state)
override fun <T : BlockEntity> getTicker(world: Level, blockState: BlockState, blockWithEntityType: BlockEntityType<T>): BlockEntityTicker<T>? = createTickerHelper(blockWithEntityType, CobblemonBlockEntities.BERRY, BerryBlockEntity.TICKER)
// Use Random Tick instead of BlockEntityTicker for performance optimization
override fun <T : BlockEntity> getTicker(world: Level, blockState: BlockState, blockWithEntityType: BlockEntityType<T>): BlockEntityTicker<T>? = null
// Enable Random Tick: only immature and non-rooted berries need to tick
override fun isRandomlyTicking(state: BlockState): Boolean {
return state.getValue(AGE) < FRUIT_AGE && !state.getValue(IS_ROOTED)
}
// Random Tick handler: uses time compensation to ensure correct growth speed
@Deprecated("Deprecated in Java")
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return
// processTick calculates elapsed time and updates timer, returns whether growth is needed
if (blockEntity.processTick(world, pos, state) && !world.getBlockTicks().hasScheduledTick(pos, this)) {
// Schedule growth for next tick to avoid tick scheduler conflicts during randomTick
world.scheduleTick(pos, this, 1)
}
}
// Scheduled tick handler: performs the actual growth
@Deprecated("Deprecated in Java")
override fun tick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
if (state.getValue(IS_ROOTED)) return
growHelper(world, random, pos, state)
}
init {
registerDefaultState(stateDefinition.any()
@@ -65,6 +65,9 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
private val growthPoints = arrayListOf<ResourceLocation>()
var mulchVariant = MulchVariant.NONE
// For Random Tick compensation: records the game time of last tick
var lastTickTime: Long = 0L
/**
* The idea behind the growth point sequence is it's a 16-long string of
* hexadecimal numbers. They represent the order of the growth points that
@@ -267,6 +270,7 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
} catch (ignored: ResourceLocationException) {}
}
this.mulchDuration = nbt.getInt(MULCH_DURATION)
this.lastTickTime = nbt.getLong(LAST_TICK_TIME)
this.wasLoading = false
if (nbt.contains(GROWTH_POINTS_SEQUENCE)) {
growthPointSequence = nbt.getString(GROWTH_POINTS_SEQUENCE)
@@ -287,6 +291,7 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
nbt.putInt(MULCH_DURATION, mulchDuration)
nbt.putString(GROWTH_POINTS_SEQUENCE, growthPointSequence)
nbt.putString(MULCH_VARIANT, mulchVariant.toString())
nbt.putLong(LAST_TICK_TIME, lastTickTime)
}
override fun setChanged() {
@@ -346,24 +351,39 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
return
}
/**
* Process Random Tick compensation logic.
* Calculates elapsed time since last tick and batch-decrements stageTimer.
*/
fun processTick(world: ServerLevel, pos: BlockPos, state: BlockState): Boolean {
if (state.getValue(BerryBlock.IS_ROOTED)) return false
val currentTime = world.gameTime
val elapsed = if (lastTickTime > 0L) {
(currentTime - lastTickTime).toInt().coerceAtLeast(1)
} else {
1 // First tick, only decrement by 1
}
lastTickTime = currentTime
// Batch decrement the timer
if (stageTimer > 0) {
stageTimer = (stageTimer - elapsed).coerceAtLeast(0)
}
// Return whether growth is needed
return stageTimer <= 0
}
//Using a similar approach to ComputerCraft, implement this client side
interface RenderState : AutoCloseable {
var needsRebuild: Boolean
}
companion object {
internal val TICKER = BlockEntityTicker<BerryBlockEntity> { world, pos, state, blockEntity ->
if (world.isClientSide) return@BlockEntityTicker
if (state.getValue(BerryBlock.IS_ROOTED)) return@BlockEntityTicker
if (blockEntity.stageTimer >= 0) {
blockEntity.stageTimer--
}
if (blockEntity.stageTimer <= 0) {
(state.block as BerryBlock).growHelper(world as ServerLevel, world.random, pos, state)
}
}
//private const val LIFE_CYCLES = "life_cycles"
private const val GROWTH_POINTS = "GrowthPoints"
private const val LAST_TICK_TIME = "LastTickTime"
private const val GROWTH_POINTS_SEQUENCE = "GrowthPointsSequence"
private const val GROWTH_TIMER = "GrowthTimer"
private const val STAGE_TIMER = "StageTimer"