mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-09 07:20:04 -04:00
refactor particle renderer, tests
This commit is contained in:
parent
ffcbd93813
commit
77ba81b4b7
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.gui.rendering.particle
|
||||
|
||||
import de.bixilon.kotlinglm.vec3.Vec3d
|
||||
import de.bixilon.kutil.observer.DataObserver
|
||||
import de.bixilon.kutil.reflection.ReflectionUtil.forceSet
|
||||
import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft
|
||||
import de.bixilon.minosoft.data.registries.particle.ParticleType
|
||||
import de.bixilon.minosoft.data.registries.particle.data.ParticleData
|
||||
import de.bixilon.minosoft.gui.rendering.RenderContext
|
||||
import de.bixilon.minosoft.gui.rendering.RenderingStates
|
||||
import de.bixilon.minosoft.gui.rendering.camera.Camera
|
||||
import de.bixilon.minosoft.gui.rendering.light.RenderLight
|
||||
import de.bixilon.minosoft.gui.rendering.particle.types.Particle
|
||||
import de.bixilon.minosoft.gui.rendering.system.dummy.DummyRenderSystem
|
||||
import de.bixilon.minosoft.gui.rendering.system.dummy.texture.DummyTextureManager
|
||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.EMPTY
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.createConnection
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
|
||||
import de.bixilon.minosoft.test.ITUtil.allocate
|
||||
import org.testng.Assert.assertEquals
|
||||
import org.testng.Assert.assertFalse
|
||||
import org.testng.annotations.Test
|
||||
|
||||
@Test(groups = ["particle"])
|
||||
class ParticleRendererTest {
|
||||
|
||||
private fun create(): ParticleRenderer {
|
||||
val context = RenderContext::class.java.allocate()
|
||||
context::connection.forceSet(createConnection(1))
|
||||
context::state.forceSet(DataObserver(RenderingStates.RUNNING))
|
||||
context::system.forceSet(DummyRenderSystem(context))
|
||||
context::textures.forceSet(DummyTextureManager(context))
|
||||
context::light.forceSet(RenderLight(context))
|
||||
context::camera.forceSet(Camera(context))
|
||||
val renderer = ParticleRenderer(context.connection, context)
|
||||
|
||||
|
||||
return renderer
|
||||
}
|
||||
|
||||
private fun ParticleRenderer.draw() {
|
||||
prePrepareDraw()
|
||||
prepareDrawAsync()
|
||||
postPrepareDraw()
|
||||
}
|
||||
|
||||
|
||||
fun setup() {
|
||||
create()
|
||||
}
|
||||
|
||||
fun `draw once`() {
|
||||
val renderer = create()
|
||||
assertEquals(renderer.size, 0)
|
||||
val particle = TestParticle(renderer.context.connection)
|
||||
renderer += particle
|
||||
renderer.draw()
|
||||
assertEquals(particle.vertices, 1)
|
||||
assertEquals(particle.tryTicks, 1)
|
||||
assertFalse(particle.dead)
|
||||
assertEquals(renderer.size, 1)
|
||||
}
|
||||
|
||||
fun `draw twice`() {
|
||||
val renderer = create()
|
||||
val particle = TestParticle(renderer.context.connection)
|
||||
renderer += particle
|
||||
renderer.draw(); renderer.draw()
|
||||
assertEquals(particle.vertices, 2)
|
||||
assertEquals(particle.tryTicks, 2)
|
||||
assertEquals(renderer.size, 1)
|
||||
}
|
||||
|
||||
fun kill() {
|
||||
val renderer = create()
|
||||
val particle = TestParticle(renderer.context.connection)
|
||||
renderer += particle
|
||||
renderer.draw(); renderer.draw()
|
||||
particle.dead = true
|
||||
renderer.draw()
|
||||
assertEquals(particle.vertices, 2)
|
||||
assertEquals(particle.tryTicks, 2)
|
||||
assertEquals(renderer.size, 0)
|
||||
}
|
||||
|
||||
fun `add 2 particles`() {
|
||||
val renderer = create()
|
||||
assertEquals(renderer.size, 0)
|
||||
val a = TestParticle(renderer.context.connection)
|
||||
val b = TestParticle(renderer.context.connection)
|
||||
renderer += a; renderer += b
|
||||
assertEquals(renderer.size, 0) // queue not updated yet
|
||||
renderer.draw()
|
||||
assertEquals(renderer.size, 2)
|
||||
assertEquals(a.vertices, 1); assertEquals(a.tryTicks, 1)
|
||||
assertEquals(b.vertices, 1); assertEquals(b.tryTicks, 1)
|
||||
}
|
||||
|
||||
fun `discard with maxAmount`() {
|
||||
val renderer = create()
|
||||
assertEquals(renderer.size, 0)
|
||||
renderer.maxAmount = 1
|
||||
val a = TestParticle(renderer.context.connection)
|
||||
val b = TestParticle(renderer.context.connection)
|
||||
renderer += a; renderer += b
|
||||
assertEquals(renderer.size, 0) // queue not updated yet
|
||||
renderer.draw()
|
||||
assertEquals(renderer.size, 1)
|
||||
assertEquals(a.vertices, 1); assertEquals(a.tryTicks, 1)
|
||||
assertEquals(b.vertices, 0); assertEquals(b.tryTicks, 0)
|
||||
}
|
||||
|
||||
|
||||
// TODO: queue, maxAmount, auto ticking
|
||||
|
||||
private class TestParticle(connection: PlayConnection) : Particle(connection, Vec3d.EMPTY, Vec3d.EMPTY, DATA) {
|
||||
var vertices = 0
|
||||
var tryTicks = 0
|
||||
|
||||
init {
|
||||
maxAge = 10
|
||||
}
|
||||
|
||||
override fun tryTick(time: Long) {
|
||||
tryTicks++
|
||||
}
|
||||
|
||||
override fun addVertex(mesh: ParticleMesh, translucentMesh: ParticleMesh, time: Long) {
|
||||
vertices++
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
val DATA = ParticleData(ParticleType(minosoft("test"), emptyList(), false, null))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.gui.rendering.particle
|
||||
|
||||
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
|
||||
import de.bixilon.minosoft.gui.rendering.particle.types.Particle
|
||||
|
||||
class ParticleList(maxAmount: Int) {
|
||||
val particles: MutableList<Particle> = ArrayList(maxAmount)
|
||||
val lock = SimpleLock()
|
||||
|
||||
val size get() = particles.size
|
||||
|
||||
fun clear() {
|
||||
lock.lock()
|
||||
particles.clear()
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.gui.rendering.particle
|
||||
|
||||
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
|
||||
import de.bixilon.minosoft.gui.rendering.particle.types.Particle
|
||||
|
||||
class ParticleQueue(val renderer: ParticleRenderer) {
|
||||
private val lock = SimpleLock()
|
||||
private val queue: MutableList<Particle> = ArrayList(QUEUE_CAPACITY)
|
||||
|
||||
|
||||
operator fun plusAssign(particle: Particle) = queue(particle)
|
||||
fun queue(particle: Particle) {
|
||||
lock.lock()
|
||||
val size = queue.size
|
||||
if (size > QUEUE_CAPACITY || renderer.size + size > renderer.maxAmount) {
|
||||
// already overloaded, ignore
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
queue += particle
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
|
||||
fun clear() {
|
||||
lock.lock()
|
||||
queue.clear()
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
fun add(list: MutableList<Particle>) {
|
||||
if (queue.isEmpty()) return
|
||||
lock.lock()
|
||||
|
||||
while (queue.isNotEmpty() && list.size < renderer.maxAmount) {
|
||||
list.add(queue.removeFirst())
|
||||
}
|
||||
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
const val QUEUE_CAPACITY = 1000
|
||||
}
|
||||
}
|
@ -14,12 +14,8 @@
|
||||
package de.bixilon.minosoft.gui.rendering.particle
|
||||
|
||||
import de.bixilon.kotlinglm.vec3.Vec3
|
||||
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
|
||||
import de.bixilon.kutil.concurrent.schedule.RepeatedTask
|
||||
import de.bixilon.kutil.concurrent.schedule.TaskScheduler
|
||||
import de.bixilon.kutil.latch.AbstractLatch
|
||||
import de.bixilon.kutil.observer.DataObserver.Companion.observe
|
||||
import de.bixilon.kutil.time.TimeUtil.millis
|
||||
import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft
|
||||
import de.bixilon.minosoft.data.world.particle.AbstractParticleRenderer
|
||||
import de.bixilon.minosoft.gui.rendering.RenderContext
|
||||
@ -35,10 +31,7 @@ import de.bixilon.minosoft.gui.rendering.system.base.layer.TranslucentLayer
|
||||
import de.bixilon.minosoft.gui.rendering.system.base.phases.SkipAll
|
||||
import de.bixilon.minosoft.modding.event.listener.CallbackEventListener.Companion.listen
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnectionStates
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnectionStates.Companion.disconnected
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.play.block.chunk.ChunkUtil.isInViewDistance
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||
import de.bixilon.minosoft.util.collections.floats.BufferedArrayFloatList
|
||||
|
||||
|
||||
@ -53,49 +46,33 @@ class ParticleRenderer(
|
||||
private val translucentShader = renderSystem.createShader(minosoft("particle")) { ParticleShader(it) }
|
||||
|
||||
// There is no opaque mesh because it is simply not needed (every particle has transparency)
|
||||
private var mesh = ParticleMesh(context, BufferedArrayFloatList(MAXIMUM_AMOUNT * ParticleMesh.ParticleMeshStruct.FLOATS_PER_VERTEX))
|
||||
private var translucentMesh = ParticleMesh(context, BufferedArrayFloatList(MAXIMUM_AMOUNT * ParticleMesh.ParticleMeshStruct.FLOATS_PER_VERTEX))
|
||||
var mesh = ParticleMesh(context, BufferedArrayFloatList(profile.maxAmount * ParticleMesh.ParticleMeshStruct.FLOATS_PER_VERTEX))
|
||||
var translucentMesh = ParticleMesh(context, BufferedArrayFloatList(profile.maxAmount * ParticleMesh.ParticleMeshStruct.FLOATS_PER_VERTEX))
|
||||
|
||||
private val particlesLock = SimpleLock()
|
||||
private var particles: MutableList<Particle> = mutableListOf()
|
||||
private var particleQueueLock = SimpleLock()
|
||||
private var particleQueue: MutableList<Particle> = mutableListOf()
|
||||
val particles = ParticleList(profile.maxAmount)
|
||||
val queue = ParticleQueue(this)
|
||||
val ticker = ParticleTicker(this)
|
||||
private var matrixUpdate = true
|
||||
|
||||
|
||||
private lateinit var particleTask: RepeatedTask
|
||||
|
||||
override val skipAll: Boolean
|
||||
get() = !enabled
|
||||
|
||||
|
||||
private var enabled = true
|
||||
var enabled = true
|
||||
set(value) {
|
||||
if (!value) {
|
||||
particlesLock.lock()
|
||||
particles.clear()
|
||||
particlesLock.unlock()
|
||||
|
||||
particleQueueLock.lock()
|
||||
particleQueue.clear()
|
||||
particleQueueLock.unlock()
|
||||
queue.clear()
|
||||
}
|
||||
field = value
|
||||
}
|
||||
private var maxAmount = MAXIMUM_AMOUNT
|
||||
var maxAmount = MAXIMUM_AMOUNT
|
||||
set(value) {
|
||||
check(value > 1) { "Can not have negative particle max amount" }
|
||||
particlesLock.lock()
|
||||
while (particles.size > value) {
|
||||
particles.removeAt(0)
|
||||
if (value < 0) throw IllegalStateException("Can not set negative amount of particles!")
|
||||
if (value < field) {
|
||||
removeAllParticles()
|
||||
}
|
||||
val particlesSize = particles.size
|
||||
particlesLock.unlock()
|
||||
particleQueueLock.lock()
|
||||
while (particlesSize + particleQueue.size > value) {
|
||||
particleQueue.removeAt(0)
|
||||
}
|
||||
particleQueueLock.unlock()
|
||||
field = value
|
||||
}
|
||||
|
||||
@ -107,90 +84,45 @@ class ParticleRenderer(
|
||||
layers.register(TranslucentLayer, translucentShader, this::drawTranslucent)
|
||||
}
|
||||
|
||||
private fun loadTextures() {
|
||||
for (particle in connection.registries.particleType) {
|
||||
for (file in particle.textures) {
|
||||
context.textures.static.create(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun init(latch: AbstractLatch) {
|
||||
profile::maxAmount.observe(this, true) { maxAmount = minOf(it, MAXIMUM_AMOUNT) }
|
||||
profile::enabled.observe(this, true) { enabled = it }
|
||||
|
||||
connection.events.listen<CameraMatrixChangeEvent> {
|
||||
matrixUpdate = true
|
||||
}
|
||||
connection.events.listen<CameraMatrixChangeEvent> { matrixUpdate = true }
|
||||
|
||||
mesh.load()
|
||||
translucentMesh.load()
|
||||
for (particle in connection.registries.particleType) {
|
||||
for (resourceLocation in particle.textures) {
|
||||
context.textures.static.create(resourceLocation)
|
||||
}
|
||||
}
|
||||
|
||||
loadTextures()
|
||||
DefaultParticleBehavior.register(connection, this)
|
||||
}
|
||||
|
||||
override fun postInit(latch: AbstractLatch) {
|
||||
shader.load()
|
||||
translucentShader.load()
|
||||
ticker.init()
|
||||
|
||||
connection.world.particle = this
|
||||
|
||||
particleTask = RepeatedTask(ProtocolDefinition.TICK_TIME, maxDelay = ProtocolDefinition.TICK_TIME / 2) {
|
||||
if (!context.state.running || !enabled || connection.state != PlayConnectionStates.PLAYING) {
|
||||
return@RepeatedTask
|
||||
}
|
||||
val cameraPosition = connection.player.physics.positionInfo.chunkPosition
|
||||
val particleViewDistance = connection.world.view.particleViewDistance
|
||||
|
||||
|
||||
particlesLock.lock()
|
||||
try {
|
||||
val time = millis()
|
||||
val iterator = particles.iterator()
|
||||
for (particle in iterator) {
|
||||
if (!particle.chunkPosition.isInViewDistance(particleViewDistance, cameraPosition)) { // ToDo: Check fog distance
|
||||
particle.dead = true
|
||||
iterator.remove()
|
||||
} else if (particle.dead) {
|
||||
iterator.remove()
|
||||
} else {
|
||||
particle.tryTick(time)
|
||||
}
|
||||
}
|
||||
|
||||
particleQueueLock.lock()
|
||||
particles += particleQueue
|
||||
particleQueue.clear()
|
||||
particleQueueLock.unlock()
|
||||
} finally {
|
||||
particlesLock.unlock()
|
||||
}
|
||||
}
|
||||
TaskScheduler += particleTask
|
||||
|
||||
connection::state.observe(this) {
|
||||
if (!it.disconnected) {
|
||||
return@observe
|
||||
}
|
||||
TaskScheduler -= particleTask
|
||||
}
|
||||
}
|
||||
|
||||
override fun addParticle(particle: Particle) {
|
||||
if (!context.state.running || !enabled) {
|
||||
return
|
||||
}
|
||||
val particleCount = particles.size + particleQueue.size
|
||||
if (particleCount >= maxAmount) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!particle.chunkPosition.isInViewDistance(connection.world.view.particleViewDistance, connection.player.physics.positionInfo.chunkPosition)) {
|
||||
particle.dead = true
|
||||
return
|
||||
}
|
||||
particle.tryTick(millis())
|
||||
|
||||
particleQueueLock.lock()
|
||||
particleQueue += particle
|
||||
particleQueueLock.unlock()
|
||||
queue += particle
|
||||
}
|
||||
|
||||
private fun updateShaders() {
|
||||
@ -214,34 +146,16 @@ class ParticleRenderer(
|
||||
translucentMesh.unload()
|
||||
}
|
||||
|
||||
override fun prepareDrawAsync() {
|
||||
private fun prepareMesh() {
|
||||
mesh.data.clear()
|
||||
translucentMesh.data.clear()
|
||||
mesh = ParticleMesh(context, mesh.data)
|
||||
translucentMesh = ParticleMesh(context, translucentMesh.data)
|
||||
}
|
||||
|
||||
particlesLock.acquire()
|
||||
|
||||
val start = millis()
|
||||
var time = start
|
||||
for ((index, particle) in particles.withIndex()) {
|
||||
particle.tryTick(time)
|
||||
if (particle.dead) {
|
||||
continue
|
||||
}
|
||||
particle.addVertex(mesh, translucentMesh, time)
|
||||
|
||||
if (index % 1000 == 0) {
|
||||
time = millis()
|
||||
if (time - start > MAX_FRAME_TIME) {
|
||||
// particles are heavily lagging out the game, reducing it.
|
||||
// TODO: introduce slow particle mode and optimize out slow particles
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
particlesLock.release()
|
||||
override fun prepareDrawAsync() {
|
||||
prepareMesh()
|
||||
ticker.tick(true)
|
||||
}
|
||||
|
||||
override fun postPrepareDraw() {
|
||||
@ -258,18 +172,12 @@ class ParticleRenderer(
|
||||
}
|
||||
|
||||
override fun removeAllParticles() {
|
||||
particlesLock.lock()
|
||||
particles.clear()
|
||||
particlesLock.unlock()
|
||||
particleQueueLock.lock()
|
||||
particleQueue.clear()
|
||||
particleQueueLock.unlock()
|
||||
queue.clear()
|
||||
}
|
||||
|
||||
|
||||
companion object : RendererBuilder<ParticleRenderer> {
|
||||
const val MAXIMUM_AMOUNT = 50000
|
||||
const val MAX_FRAME_TIME = 5
|
||||
|
||||
override fun build(connection: PlayConnection, context: RenderContext): ParticleRenderer? {
|
||||
if (connection.profiles.particle.skipLoading) {
|
||||
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.gui.rendering.particle
|
||||
|
||||
import de.bixilon.kutil.concurrent.schedule.RepeatedTask
|
||||
import de.bixilon.kutil.concurrent.schedule.TaskScheduler
|
||||
import de.bixilon.kutil.exception.ExceptionUtil.ignoreAll
|
||||
import de.bixilon.kutil.observer.DataObserver.Companion.observe
|
||||
import de.bixilon.kutil.time.TimeUtil.millis
|
||||
import de.bixilon.minosoft.data.world.positions.ChunkPosition
|
||||
import de.bixilon.minosoft.gui.rendering.particle.types.Particle
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnectionStates
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.play.block.chunk.ChunkUtil.isInViewDistance
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||
|
||||
class ParticleTicker(val renderer: ParticleRenderer) {
|
||||
private val particles = renderer.particles
|
||||
private val context = renderer.context
|
||||
private var task: RepeatedTask? = null
|
||||
|
||||
|
||||
private fun canTick(): Boolean {
|
||||
if (context.connection.state != PlayConnectionStates.PLAYING) return false
|
||||
if (!renderer.enabled) return false
|
||||
if (!context.state.running) return false
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun Particle.tick(viewDistance: Int, cameraPosition: ChunkPosition, millis: Long) {
|
||||
if (!chunkPosition.isInViewDistance(viewDistance, cameraPosition)) { // ToDo: Check fog distance
|
||||
dead = true
|
||||
}
|
||||
if (dead) return
|
||||
ignoreAll { tryTick(millis) }
|
||||
}
|
||||
|
||||
fun tick(collect: Boolean) {
|
||||
if (!canTick()) return
|
||||
|
||||
val camera = context.connection.camera.entity.physics.positionInfo
|
||||
val cameraPosition = camera.chunkPosition
|
||||
val viewDistance = context.connection.world.view.particleViewDistance
|
||||
val start = millis()
|
||||
var time = start
|
||||
|
||||
|
||||
particles.lock.lock()
|
||||
renderer.queue.add(particles.particles)
|
||||
|
||||
val iterator = particles.particles.iterator()
|
||||
var index = 0
|
||||
for (particle in iterator) {
|
||||
particle.tick(viewDistance, cameraPosition, time)
|
||||
|
||||
if (particle.dead) {
|
||||
iterator.remove()
|
||||
continue
|
||||
}
|
||||
if (collect) {
|
||||
particle.addVertex(renderer.mesh, renderer.translucentMesh, time)
|
||||
}
|
||||
if (index % 1000 == 0) {
|
||||
// check periodically if time is exceeded
|
||||
time = millis()
|
||||
if (time - start > MAX_TICK_TIME) {
|
||||
break
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
renderer.queue.add(particles.particles)
|
||||
particles.lock.unlock()
|
||||
}
|
||||
|
||||
private fun unregister() {
|
||||
val task = this.task ?: return
|
||||
TaskScheduler -= task
|
||||
this.task = null
|
||||
}
|
||||
|
||||
private fun register() {
|
||||
if (this.task != null) unregister()
|
||||
val task = RepeatedTask(ProtocolDefinition.TICK_TIME, maxDelay = ProtocolDefinition.TICK_TIME / 2) { tick(false) }
|
||||
this.task = task
|
||||
TaskScheduler += task
|
||||
}
|
||||
|
||||
|
||||
fun init() {
|
||||
context.connection::state.observe(this) {
|
||||
unregister()
|
||||
if (it == PlayConnectionStates.PLAYING) {
|
||||
register()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_TICK_TIME = 5
|
||||
}
|
||||
}
|
@ -180,7 +180,7 @@ abstract class Particle(
|
||||
forceMove(velocity)
|
||||
}
|
||||
|
||||
fun tryTick(time: Long) {
|
||||
open fun tryTick(time: Long) {
|
||||
if (dead) {
|
||||
return
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user