Skip to content

Commit

Permalink
almost
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-s168 committed Jan 19, 2024
1 parent 8dbee1e commit 6eb807e
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 24 deletions.
7 changes: 6 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
- [ ] Better thruster particles
- [ ] New ship assembler
- [ ] Fix propellers rendering weird underwater (something alpha blend something)
- [ ] Thruster particles are sometimes in the wrong direction

## Code
- [ ] Better ship assembler structure finder algorithm
- [ ] Use tab create function in TournamentPlatformHelper
- [ ] Use tab create function in TournamentPlatformHelper
- [ ] Proper logging

## Others
- [ ] Make big propeller work if water below or above the prop too
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import org.valkyrienskies.tournament.registry.RegistrySupplier
object TournamentBlockEntities {
private val BLOCKENTITIES = DeferredRegister.create(TournamentMod.MOD_ID, Registry.BLOCK_ENTITY_TYPE_REGISTRY)

val SENSOR = TournamentBlocks.SENSOR withBE ::SensorBlockEntity byName "sensor"
val ROPE_HOOK = TournamentBlocks.ROPE_HOOK withBE ::RopeHookBlockEntity byName "rope_hook"
val PROP_BIG = TournamentBlocks.PROP_BIG withBE ::BigPropellerBlockEntity byName "prop_big"
val PROP_SMALL = TournamentBlocks.PROP_SMALL withBE ::SmallPropellerBlockEntity byName "prop_small"
val SENSOR = TournamentBlocks.SENSOR withBE ::SensorBlockEntity byName "sensor"
val ROPE_HOOK = TournamentBlocks.ROPE_HOOK withBE ::RopeHookBlockEntity byName "rope_hook"
val PROP_BIG = TournamentBlocks.PROP_BIG withBE ::BigPropellerBlockEntity byName "prop_big"
val PROP_SMALL = TournamentBlocks.PROP_SMALL withBE ::SmallPropellerBlockEntity byName "prop_small"
val CHUNK_LOADER = TournamentBlocks.CHUNK_LOADER withBE ::ChunkLoaderBlockEntity byName "chunk_loader"

// explosives:
val EXPLOSIVE = TournamentBlocks.EXPLOSIVE_INSTANT_SMALL withBE ::ExplosiveBlockEntity byName "explosive_instant_small"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ object TournamentBlocks {
lateinit var SENSOR : RegistrySupplier<SensorBlock>
lateinit var PROP_BIG : RegistrySupplier<PropellerBlock>
lateinit var PROP_SMALL : RegistrySupplier<PropellerBlock>
lateinit var CHUNK_LOADER : RegistrySupplier<ChunkLoaderBlock>

lateinit var EXPLOSIVE_INSTANT_SMALL : RegistrySupplier<AbstractExplosiveBlock>
lateinit var EXPLOSIVE_INSTANT_MEDIUM : RegistrySupplier<AbstractExplosiveBlock>
Expand All @@ -56,9 +57,9 @@ object TournamentBlocks {
POWERED_BALLOON = register("balloon", ::PoweredBalloonBlock)
BALLOON = register("balloon_unpowered", ::BalloonBlock)
FLOATER = register("floater") { Block(
BlockBehaviour.Properties.of(Material.WOOD)
.sound(SoundType.WOOD)
.strength(1.0f, 2.0f)
BlockBehaviour.Properties.of(Material.WOOD)
.sound(SoundType.WOOD)
.strength(1.0f, 2.0f)
)}
THRUSTER = register("thruster") {
ThrusterBlock(
Expand Down Expand Up @@ -100,6 +101,7 @@ object TournamentBlocks {
::SmallPropellerBlockEntity
)
}
CHUNK_LOADER = register("chunk_loader", ::ChunkLoaderBlock)

EXPLOSIVE_INSTANT_SMALL = register("explosive_instant_small") { object : AbstractExplosiveBlock() {
override fun explode(level: ServerLevel, pos: BlockPos) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ object TournamentConfig {
@JsonSchema(description = "The acceleration of a big propeller. (deaccel = accel * 2)")
var propellerSmallAccel = 1.0f

@JsonSchema(description = "How many chunk tickets can be processed each level tick? (-1 means unlimited)")
var chunkTicketsPerTick = -1

@JsonSchema(description = "How many chunks can be loaded per chunk ticket?")
var chunksPerTicket = 100

@JsonSchema(description = "After how many ticks to error when loading chunk still not finished? (throws error when double this amount of ticks has passed)")
var chunkLoadTimeout = 40

// TODO: add stuff idk
@JsonSchema(description = "The list of blocks that don't get assembled by the ship assembler")
var blockBlacklist = setOf(
"minecraft:dirt",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.valkyrienskies.tournament.blockentity

import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.state.BlockState
import org.joml.Vector3d
import org.valkyrienskies.mod.common.getShipObjectManagingPos
import org.valkyrienskies.mod.common.util.toJOMLD
import org.valkyrienskies.tournament.TournamentBlockEntities
import org.valkyrienskies.tournament.chunk.ChunkLoader
import org.valkyrienskies.tournament.chunk.ChunkLoaderManager
import org.valkyrienskies.tournament.chunk.ChunkLoadingTicket

class ChunkLoaderBlockEntity(pos: BlockPos, state: BlockState):
BlockEntity(TournamentBlockEntities.CHUNK_LOADER.get(), pos, state),
ChunkLoader
{

internal var ticket: ChunkLoadingTicket? = null

fun tick(level: ServerLevel) {
if (ticket == null) {
val manager = ChunkLoaderManager.getFor(level)
ticket = manager.allocate(this, 200)
}
}

private fun getCurrPos() =
level
?.getShipObjectManagingPos(blockPos)
?.shipToWorld
?.transformPosition(blockPos.toJOMLD())

override fun getCurrentChunk(): ChunkPos =
getCurrPos()
?.let {
ChunkPos(it.x.toInt() shr 4, it.z.toInt() shr 4)
} ?: ChunkPos(blockPos.x shr 4, blockPos.z shr 4)

override fun getFutureChunk(): ChunkPos =
level
?.getShipObjectManagingPos(blockPos)
?.velocity
?.add(getCurrPos(), Vector3d())
?.let {
ChunkPos(it.x.toInt() shr 4, it.z.toInt() shr 4)
} ?: ChunkPos(blockPos.x shr 4, blockPos.z shr 4)

companion object {
val ticker = BlockEntityTicker<ChunkLoaderBlockEntity> { level, _, _, be ->
if(level !is ServerLevel)
return@BlockEntityTicker

be.tick(level)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.valkyrienskies.tournament.blocks

import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.material.Material
import org.valkyrienskies.tournament.blockentity.ChunkLoaderBlockEntity

class ChunkLoaderBlock: BaseEntityBlock(
Properties.of(Material.METAL)
) {
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity =
ChunkLoaderBlockEntity(pos, state)

@Suppress("UNCHECKED_CAST")
override fun <T: BlockEntity> getTicker(
level: Level,
state: BlockState,
blockEntityType: BlockEntityType<T>
): BlockEntityTicker<T> =
ChunkLoaderBlockEntity.ticker as BlockEntityTicker<T>

@Deprecated("Deprecated in Java")
override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, isMoving: Boolean) {
if (level is ServerLevel) {
val be = level.getBlockEntity(pos) as? ChunkLoaderBlockEntity
?: return

be.ticket?.dispose()
}

super.onRemove(state, level, pos, newState, isMoving)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ class PropellerBlock(
Properties.of(Material.STONE)
.sound(SoundType.STONE)
.strength(1.0f, 2.0f)

), RedstoneConnectingBlock {
companion object {
private val SHAPE = RotShapes.box(0.1, 0.1, 8.1, 15.9, 15.9, 15.9)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package org.valkyrienskies.tournament.chunk

import net.minecraft.resources.ResourceKey
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ChunkHolder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.chunk.ChunkStatus
import org.apache.commons.lang3.mutable.MutableInt
import org.joml.Vector2i
import org.joml.primitives.Rectanglei
import org.valkyrienskies.mod.common.util.toJOML
import org.valkyrienskies.tournament.util.extension.itTake
import org.valkyrienskies.tournament.TickScheduler
import org.valkyrienskies.tournament.TournamentConfig
import org.valkyrienskies.tournament.util.extension.*
import java.util.concurrent.ConcurrentHashMap
import java.util.function.Consumer

// TODO: make multiple blocks of tickets that cycle trough every tick so that not too many chunks are tried to be loaded at once
class ChunkLoaderManager private constructor(
val level: ServerLevel
) {
Expand All @@ -16,8 +26,9 @@ class ChunkLoaderManager private constructor(
private var sorted = true

fun tick(
amountOfTickets: Int,
amountOfTickets: Int? = null,
amountOfChunksPerTicket: Int,
amountOfActualChunksPerTicket: Int,
loader: Consumer<ChunkPos>
) {
if (!sorted) {
Expand All @@ -26,24 +37,56 @@ class ChunkLoaderManager private constructor(
}
sorted = true
}
tickets.itTake(amountOfTickets).forEach { ticket ->
val start = ticket.loader.getCurrentChunk().toJOML()
val end = ticket.loader.getFutureChunk().toJOML()

val dist = start.gridDistance(end)
val am = amountOfTickets ?: tickets.size

if (tickets.size > am) {
throw Exception("[Tournament] Too many chunk-loading tickets in dimension ${level.dimension()}: ${tickets.size} > $am!")
}

tickets.itTake(am).forEach { ticket ->
val mid = ticket.loader.getCurrentChunk().toJOML()
val top = ticket.loader.getFutureChunk().toJOML()
val bottom = top.sub(mid, Vector2i()).negate().add(mid)

val box = Rectanglei(
top,
bottom
).fix()

val t = box.area().toFloat() / amountOfChunksPerTicket

box .scaleFrom(factor = t, center = mid)
.values()
.sortedBy { it.distanceSquared(mid) }
.take(amountOfActualChunksPerTicket)
.map { ChunkPos(it.x, it.y) }
.filter { !processing.containsKey(it) }
.forEach(loader::accept)
}
}

fun allocate(loader: ChunkLoader, priority: Int) =
ChunkLoadingTicket(this, loader, priority).also {
fun allocate(loader: ChunkLoader, priority: Int): ChunkLoadingTicket {
val i = tickets.indexOfFirst { it.loader == loader }
if (i != -1) {
val ticket = tickets[i]
ticket.priority = priority
return ticket
}
return ChunkLoadingTicket(this, loader, priority).also {
tickets += it
if (tickets.size > 1) {
sorted = false
}
}
}

private val processing = ConcurrentHashMap<ChunkPos, MutableInt>(
TournamentConfig.SERVER.chunksPerTicket * 5
)

companion object {
internal val map =
private val map =
HashMap<ResourceKey<Level>, ChunkLoaderManager>()

fun getFor(level: ServerLevel): ChunkLoaderManager {
Expand All @@ -53,7 +96,47 @@ class ChunkLoaderManager private constructor(

val lm = ChunkLoaderManager(level)
map[dim] = lm

if (!tickTaskSet) {
tickTaskSet = true
TickScheduler.serverTickPerm(tickTask)
}

return lm
}

private var tickTaskSet = false

private val tickTask: (MinecraftServer) -> Unit = { server ->
val cpt = TournamentConfig.SERVER.chunkTicketsPerTick.let {
if (it == -1) null else it
}
server.allLevels.forEach { level ->
val manager = getFor(level)
manager.processing.forEach { (k, v) ->
val t = v.getAndAdd(1)
if (t >= TournamentConfig.SERVER.chunkLoadTimeout * 2) {
throw Exception("[Tournament] Chunk loading timed out: still not finished after $t ticks ($k)!")
}
if (t >= TournamentConfig.SERVER.chunkLoadTimeout) {
println("[Tournament] Chunk loading taking longer than expected: took $t ticks to load chunk at $k!")
}
}
manager.tick(
amountOfTickets = cpt,
amountOfChunksPerTicket = TournamentConfig.SERVER.chunksPerTicket * 10,
amountOfActualChunksPerTicket = TournamentConfig.SERVER.chunksPerTicket,
loader = { pos ->
manager.processing[pos] = MutableInt(0)
level.chunkSource.chunkMap.schedule(
ChunkHolder(pos, 0, level, level.lightEngine, null, null),
ChunkStatus.EMPTY // load chunks
).thenRun {
manager.processing.remove(pos)
}
}
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.valkyrienskies.tournament.chunk
data class ChunkLoadingTicket internal constructor(
val manager: ChunkLoaderManager,
val loader: ChunkLoader,
val priority: Int
var priority: Int
) {
var active: Boolean = true
private set
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.valkyrienskies.tournament.util.extension

// TODO: replace with Sequence<T>

fun <T> Iterable<T>.with(other: Iterable<T>): Iterable<T> =
object: Iterable<T> {
override fun iterator(): Iterator<T> =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.valkyrienskies.tournament.util.extension

import org.joml.Vector2i
import org.joml.primitives.Rectanglei

fun Rectanglei.fix(): Rectanglei =
this.also {
if (it.minX > it.maxX) {
val temp = it.minX
it.minX = it.maxX
it.maxX = temp
}
if (it.minY > it.maxY) {
val temp = it.minY
it.minY = it.maxY
it.maxY = temp
}
}

fun Rectanglei.scaleFrom(factor: Float, center: Vector2i): Rectanglei =
this.also {
val midX = center.x
val midY = center.y
val width = it.maxX - it.minX
val height = it.maxY - it.minY
val newWidth = (width * factor).toInt()
val newHeight = (height * factor).toInt()
it.minX = midX - newWidth / 2
it.maxX = midX + newWidth / 2
it.minY = midY - newHeight / 2
it.maxY = midY + newHeight / 2
}

fun Rectanglei.values(): Sequence<Vector2i> =
sequence {
for (x in minX..maxX) {
for (y in minY..maxY) {
yield(Vector2i(x, y))
}
}
}
Loading

0 comments on commit 6eb807e

Please sign in to comment.