feat(examples): four runnable plugin examples
- player-load: load-on-join (PlayerReadyEvent → IO → modify). - async-moderation: IAsyncEvent → coroutine bridge for chat moderation. - periodic-leaderboard: pluginScope loop with parallel reads. - bounty-board: kitchen-sink demo exercising every v0.1 primitive.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# periodic-leaderboard
|
||||
|
||||
Recomputes a top-5 leaderboard every minute by reading `PlayerStats.level`
|
||||
from every online player in parallel, sorting, and broadcasting.
|
||||
|
||||
## What it shows
|
||||
|
||||
- `pluginScope(this).launch { while (isActive) { delay(60.seconds); … } }` —
|
||||
a plugin-lifetime periodic task. Cancels cleanly on shutdown via
|
||||
`Async.shutdown()`.
|
||||
- Parallel `read` over players via `async { … }.awaitAll()`. Each `read`
|
||||
switches to its player's owning world dispatcher independently — reads on
|
||||
different worlds run concurrently, reads on the same world serialize.
|
||||
- `runCatching { … }` around the body so a single bad read doesn't kill the
|
||||
loop.
|
||||
|
||||
This is the shape for any plugin-wide background recompute: leaderboards,
|
||||
periodic backups of derived state, quest sync, etc.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
./gradlew shadowJar
|
||||
# → build/libs/periodic-leaderboard.jar
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
1. Drop in `mods/`, wire `PlayerStatsBinding.componentType()` and
|
||||
`onlinePlayers()`.
|
||||
2. For a faster demo while testing, change `delay(60.seconds)` to
|
||||
`delay(5.seconds)` and rebuild.
|
||||
3. Connect 2+ players. The top-5 logs every loop tick.
|
||||
|
||||
If you want **per-world** leaderboards instead, swap `pluginScope(this)` for
|
||||
`worldScope(world)` and run one loop per world. Cancellation flows through
|
||||
`WorldScopes.cancel(uuid)`.
|
||||
@@ -0,0 +1,40 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.shadow)
|
||||
}
|
||||
|
||||
group = "com.mythlane.example"
|
||||
version = "0.1.0"
|
||||
description = "Async example: periodic-leaderboard"
|
||||
|
||||
val hytaleServerVersion = libs.versions.hytaleServer.get()
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.hytale.server)
|
||||
implementation("com.mythlane:async:0.1.0-SNAPSHOT")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(25)
|
||||
compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_24) }
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile>().configureEach { options.release.set(24) }
|
||||
|
||||
tasks.processResources {
|
||||
filteringCharset = Charsets.UTF_8.name()
|
||||
val props = mapOf(
|
||||
"version" to project.version,
|
||||
"description" to (project.description ?: ""),
|
||||
"hytaleServerVersion" to hytaleServerVersion,
|
||||
)
|
||||
inputs.properties(props)
|
||||
filesMatching("manifest.json") { expand(props) }
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
archiveBaseName.set(project.name)
|
||||
archiveClassifier.set("")
|
||||
}
|
||||
tasks.jar { enabled = false }
|
||||
tasks.build { dependsOn(tasks.shadowJar) }
|
||||
@@ -0,0 +1,18 @@
|
||||
rootProject.name = "periodic-leaderboard"
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://maven.hytale.com/release") { name = "hytale" }
|
||||
}
|
||||
versionCatalogs {
|
||||
create("libs") { from(files("../../gradle/libs.versions.toml")) }
|
||||
}
|
||||
}
|
||||
|
||||
includeBuild("../..") {
|
||||
dependencySubstitution {
|
||||
substitute(module("com.mythlane:async"))
|
||||
.using(project(":dist"))
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.mythlane.example.leaderboard
|
||||
|
||||
import com.hypixel.hytale.server.core.entity.entities.Player
|
||||
import com.hypixel.hytale.server.core.plugin.JavaPlugin
|
||||
import com.hypixel.hytale.server.core.plugin.JavaPluginInit
|
||||
import com.mythlane.async.Async
|
||||
import com.mythlane.async.ecs.ComponentRegistry
|
||||
import com.mythlane.async.ecs.read
|
||||
import com.mythlane.async.binding.handle
|
||||
import com.mythlane.async.binding.installAsync
|
||||
import com.mythlane.async.binding.pluginScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* Demonstrates: `pluginScope(this).launch { while (isActive) { delay(…); … } }`
|
||||
* for a periodic task, plus parallel `read<T>` via `async { … }.awaitAll()`.
|
||||
*
|
||||
* SDK-side wiring of `onlinePlayers()` and the component type is left as `TODO`.
|
||||
*/
|
||||
class LeaderboardPlugin(init: JavaPluginInit) : JavaPlugin(init) {
|
||||
|
||||
override fun start() {
|
||||
installAsync()
|
||||
ComponentRegistry.register<PlayerStats>(PlayerStatsBinding.componentType())
|
||||
|
||||
pluginScope(this).launch {
|
||||
while (isActive) {
|
||||
delay(60.seconds)
|
||||
runCatching { recompute() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun shutdown() {
|
||||
Async.shutdown()
|
||||
}
|
||||
|
||||
private suspend fun recompute() = coroutineScope {
|
||||
val players: List<Player> = onlinePlayers()
|
||||
players.map { p ->
|
||||
async {
|
||||
p to read<PlayerStats, Int>(p.handle()) { level }
|
||||
}
|
||||
}.awaitAll()
|
||||
.sortedByDescending { it.second }
|
||||
.take(5)
|
||||
// .also { broadcast(...) } — wire to your messaging API
|
||||
}
|
||||
|
||||
private fun onlinePlayers(): List<Player> = TODO("Return Players from your dev server (e.g. HytaleServer.get().worlds...).")
|
||||
}
|
||||
|
||||
/** Stub component shape — replace with your real `Component<EntityStore>` subclass. */
|
||||
class PlayerStats { var level: Int = 1 }
|
||||
|
||||
object PlayerStatsBinding {
|
||||
fun componentType(): Any = TODO("Return EntityStore.REGISTRY.register(PlayerStats::class.java, ...) result here.")
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"Group": "Mythlane",
|
||||
"Name": "PeriodicLeaderboard",
|
||||
"Version": "${version}",
|
||||
"Description": "${description}",
|
||||
"Authors": [
|
||||
{ "Name": "Mythlane", "Email": "contact@mythlane.com", "Url": "https://mythlane.com" }
|
||||
],
|
||||
"Website": "https://mythlane.com",
|
||||
"Main": "com.mythlane.example.leaderboard.LeaderboardPlugin",
|
||||
"ServerVersion": "${hytaleServerVersion}",
|
||||
"Dependencies": {},
|
||||
"OptionalDependencies": {},
|
||||
"DisabledByDefault": false,
|
||||
"IncludesAssetPack": false
|
||||
}
|
||||
Reference in New Issue
Block a user