-
Notifications
You must be signed in to change notification settings - Fork 10
PylonTickingEntity #483
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
PylonTickingEntity #483
Changes from all commits
06ce705
ed9b3ce
42efef7
a430ecb
c23b084
0f25159
847c3fe
14cb5f0
4c6c5fb
b03306d
1b12b5e
d1bb005
fc90637
31b771c
38d592c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
|---|---|---|
|
|
@@ -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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| e.printStackTrace() | ||
| entityErrMap[entity.uuid] = entityErrMap[entity.uuid]?.plus(1) ?: 1 | ||
| if (entityErrMap[entity.uuid]!! > PylonConfig.ALLOWED_ENTITY_ERRORS) { | ||
|
|
||
Intybyte marked this conversation as resolved.
Show resolved
Hide resolved
|
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() { | ||
Intybyte marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| override fun getHandlers(): HandlerList | ||
| = handlerList | ||
|
|
||
| companion object { | ||
| @JvmStatic | ||
| val handlerList: HandlerList = HandlerList() | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.