Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,7 @@ object PylonSerializers {
@JvmSynthetic
internal val TICKING_BLOCK_DATA = TickingBlockPersistentDataType

@JvmSynthetic
internal val TICKING_ENTITY_DATA = TickingEntityPersistentDataType

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.github.pylonmc.pylon.core.datatypes

import io.github.pylonmc.pylon.core.entity.base.PylonTickingEntity
import io.github.pylonmc.pylon.core.util.pylonKey
import org.bukkit.persistence.PersistentDataAdapterContext
import org.bukkit.persistence.PersistentDataContainer
import org.bukkit.persistence.PersistentDataType

internal object TickingEntityPersistentDataType : PersistentDataType<PersistentDataContainer, PylonTickingEntity.Companion.TickingEntityData> {
val tickIntervalKey = pylonKey("tick_interval")
val isAsyncKey = pylonKey("is_async")

override fun getPrimitiveType(): Class<PersistentDataContainer> = PersistentDataContainer::class.java

override fun getComplexType(): Class<PylonTickingEntity.Companion.TickingEntityData> =
PylonTickingEntity.Companion.TickingEntityData::class.java

override fun fromPrimitive(
primitive: PersistentDataContainer,
context: PersistentDataAdapterContext
): PylonTickingEntity.Companion.TickingEntityData {
val tickInterval = primitive.get(tickIntervalKey, PersistentDataType.INTEGER)!!
val isAsync = primitive.get(isAsyncKey, PersistentDataType.BOOLEAN)!!
return PylonTickingEntity.Companion.TickingEntityData(tickInterval, isAsync, null)
}

override fun toPrimitive(
complex: PylonTickingEntity.Companion.TickingEntityData,
context: PersistentDataAdapterContext
): PersistentDataContainer {
val pdc = context.newPersistentDataContainer()
pdc.set(tickIntervalKey, PersistentDataType.INTEGER, complex.tickInterval)
pdc.set(isAsyncKey, PersistentDataType.BOOLEAN, complex.isAsync)
return pdc
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,17 @@ internal object EntityListener : Listener {

@JvmSynthetic
internal fun logEventHandleErr(event: Event, e: Exception, entity: PylonEntity<*>) {
PylonCore.logger.severe("Error when handling entity(${entity.key}, ${entity.uuid}, ${entity.entity.location}) event handler ${event.javaClass.simpleName}: ${e.localizedMessage}")
PylonCore.logger.severe("Error when handling entity(${entity.key}, ${entity.uuid}, ${entity.entity.location}) event handler ${event?.javaClass?.simpleName}: ${e.localizedMessage}")
e.printStackTrace()
entityErrMap[entity.uuid] = entityErrMap[entity.uuid]?.plus(1) ?: 1
if (entityErrMap[entity.uuid]!! > PylonConfig.ALLOWED_ENTITY_ERRORS) {
entity.entity.remove()
}
}

@JvmSynthetic
internal fun logEventHandleErrTicking(e: Exception, entity: PylonEntity<*>) {
PylonCore.logger.severe("Error when handling ticking entity(${entity.key}, ${entity.uuid}, ${entity.entity.location}): ${e.localizedMessage}")
Comment on lines +818 to +820
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 small things for clarity. no event handling going on here so would be best to call it something like logTickingErr and change the message to "Error when ticking entity"

e.printStackTrace()
entityErrMap[entity.uuid] = entityErrMap[entity.uuid]?.plus(1) ?: 1
if (entityErrMap[entity.uuid]!! > PylonConfig.ALLOWED_ENTITY_ERRORS) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.github.pylonmc.pylon.core.PylonCore
import io.github.pylonmc.pylon.core.addon.PylonAddon
import io.github.pylonmc.pylon.core.block.BlockStorage
import io.github.pylonmc.pylon.core.config.PylonConfig
import io.github.pylonmc.pylon.core.event.PylonEntityAddEvent
import io.github.pylonmc.pylon.core.event.PylonEntityDeathEvent
import io.github.pylonmc.pylon.core.event.PylonEntityLoadEvent
import io.github.pylonmc.pylon.core.event.PylonEntityUnloadEvent
Expand All @@ -19,7 +20,7 @@ import org.bukkit.entity.Entity
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.world.EntitiesLoadEvent
import java.util.UUID
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.locks.ReentrantReadWriteLock
import java.util.function.Consumer
Expand Down Expand Up @@ -194,6 +195,7 @@ object EntityStorage : Listener {
delay(PylonConfig.ENTITY_DATA_AUTOSAVE_INTERVAL_SECONDS * 1000)
}
}
PylonEntityAddEvent(entity).callEvent()
entity
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package io.github.pylonmc.pylon.core.entity.base

import com.github.shynixn.mccoroutine.bukkit.asyncDispatcher
import com.github.shynixn.mccoroutine.bukkit.launch
import com.github.shynixn.mccoroutine.bukkit.minecraftDispatcher
import com.github.shynixn.mccoroutine.bukkit.ticks
import io.github.pylonmc.pylon.core.PylonCore
import io.github.pylonmc.pylon.core.config.PylonConfig
import io.github.pylonmc.pylon.core.datatypes.PylonSerializers
import io.github.pylonmc.pylon.core.entity.EntityListener
import io.github.pylonmc.pylon.core.entity.PylonEntity
import io.github.pylonmc.pylon.core.event.PylonEntityAddEvent
import io.github.pylonmc.pylon.core.event.PylonEntityDeathEvent
import io.github.pylonmc.pylon.core.event.PylonEntityLoadEvent
import io.github.pylonmc.pylon.core.event.PylonEntityUnloadEvent
import io.github.pylonmc.pylon.core.util.pylonKey
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.jetbrains.annotations.ApiStatus
import java.util.*

/**
* Represents an entity that 'ticks' (does something at a fixed time interval).
*/
interface PylonTickingEntity {

private val tickingData: TickingEntityData
get() = tickingEntities.getOrPut(this) { TickingEntityData(
PylonConfig.DEFAULT_TICK_INTERVAL,
false,
null
)}

/**
* The interval at which the [tick] function is called. You should generally use [setTickInterval]
* in your place constructor instead of overriding this.
*/
val tickInterval
get() = tickingData.tickInterval

/**
* Whether the [tick] function should be called asynchronously. You should generally use
* [setAsync] in your place constructor instead of overriding this.
*/
val isAsync
get() = tickingData.isAsync

/**
* Sets how often the [tick] function should be called (in Minecraft ticks)
*/
fun setTickInterval(tickInterval: Int) {
tickingData.tickInterval = tickInterval
}

/**
* Sets whether the [tick] function should be called asynchronously.
*
* WARNING: Setting an entity to tick asynchronously could have unintended consequences.
*
* Only set this option if you understand what 'asynchronous' means, and note that you
* cannot interact with the world asynchronously.
*/
fun setAsync(isAsync: Boolean) {
tickingData.isAsync = isAsync
}

/**
* The function that should be called periodically.
*/
fun tick()

@ApiStatus.Internal
companion object : Listener {

data class TickingEntityData(
var tickInterval: Int,
var isAsync: Boolean,
var job: Job?,
)

private val tickingEntityKey = pylonKey("ticking_entity_data")

private val tickingEntities = IdentityHashMap<PylonTickingEntity, TickingEntityData>()

@EventHandler
private fun onSerialize(event: PylonEntityAddEvent) { //todo serialize
val entity = event.pylonEntity
if (entity is PylonTickingEntity) {
entity.entity.persistentDataContainer.set(tickingEntityKey, PylonSerializers.TICKING_ENTITY_DATA, tickingEntities[entity]!!)
}
}
Comment on lines +87 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't serialization be happening on serialize, not on add? Just checked and there's no event for this... I would add PylonEntitySerialize and PylonEntityDeserialize events here for consistency with blocks, because AFAICT this will not work properly if only called on PylonEntityAdd (won't respect tick inteval set later)


@EventHandler
private fun onUnload(event: PylonEntityUnloadEvent) {
val entity = event.pylonEntity
if (entity is PylonTickingEntity) {
tickingEntities.remove(entity)?.job?.cancel()
}
}

@EventHandler
private fun onDeath(event: PylonEntityDeathEvent) {
val entity = event.pylonEntity
if (entity is PylonTickingEntity) {
tickingEntities.remove(entity)?.job?.cancel()
}
}

@EventHandler
private fun onEntityLoad(event: PylonEntityLoadEvent) {
val entity = event.pylonEntity
if (entity is PylonTickingEntity) {
startTicker(entity)
}
}

/**
* Returns true if the entity is still ticking, or false if the entity does
* not exist, is not a ticking entity, or has errored and been unloaded.
*/
@JvmStatic
@ApiStatus.Internal
fun isTicking(entity: PylonEntity<*>?): Boolean {
return entity is PylonTickingEntity && tickingEntities[entity]?.job?.isActive == true
}

@JvmSynthetic
internal fun stopTicking(entity: PylonTickingEntity) {
tickingEntities[entity]?.job?.cancel()
}

private fun startTicker(tickingEntity: PylonTickingEntity) {
val dispatcher = if (tickingEntity.isAsync) PylonCore.asyncDispatcher else PylonCore.minecraftDispatcher
tickingEntities[tickingEntity]?.job = PylonCore.launch(dispatcher) {
while (true) {
delay(tickingEntity.tickInterval.ticks)
try {
tickingEntity.tick()
} catch (e: Exception) {
PylonCore.launch(PylonCore.minecraftDispatcher) {
EntityListener.logEventHandleErrTicking(e, tickingEntity as PylonEntity<*>)
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.pylonmc.pylon.core.event

import io.github.pylonmc.pylon.core.entity.PylonEntity
import org.bukkit.event.Event
import org.bukkit.event.HandlerList

/**
* Called when an entity is added to [io.github.pylonmc.pylon.core.entity.EntityStorage],
* when this is called, the entity already spawned in the world, this only tracks when the
* pylon entity is actually added in the [io.github.pylonmc.pylon.core.entity.EntityStorage]
*/
class PylonEntityAddEvent(val pylonEntity: PylonEntity<*>) : Event() {

override fun getHandlers(): HandlerList
= handlerList

companion object {
@JvmStatic
val handlerList: HandlerList = HandlerList()
}
}
Loading