Skip to content

Commit

Permalink
Make AudienceFilterEntry#display suspendable
Browse files Browse the repository at this point in the history
  • Loading branch information
gabber235 committed Jan 21, 2025
1 parent 46a036d commit 9d64b9b
Show file tree
Hide file tree
Showing 40 changed files with 72 additions and 50 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test-building.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Build Extensions
uses: gradle/gradle-build-action@v2
with:
arguments: buildRelease --scan
arguments: test buildRelease --scan
build-root-directory: ./extensions
- name: Upload Jars as Artifacts
uses: actions/upload-artifact@v3
Expand Down
19 changes: 19 additions & 0 deletions documentation/docs/develop/02-extensions/08-api-changes/0.8.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,22 @@ class ExampleDialogueDialogueMessenger(player: Player, context: InteractionConte
}
}
```

## Suspendable `AudienceEntry#display`

To allow audience entries to have some async loading behaviour, the `display` now is a suspendable function.

```kotlin showLineNumbers
@Entry("example_audience", "An example audience entry.", Colors.GREEN, "material-symbols:chat-rounded")
class ExampleAudienceEntry(
override val id: String = "",
override val name: String = "",
) : AudienceEntry {
// highlight-red
override fun display(): AudienceDisplay {
// highlight-green
override suspend fun display(): AudienceDisplay {
return ExampleAudienceDisplay()
}
}
```
16 changes: 10 additions & 6 deletions documentation/plugins/code-snippets/snippets.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@
},
"interaction_bound": {
"file": "src/main/kotlin/com/typewritermc/example/entries/interaction/ExampleInteractionBound.kt",
"content": "@Entry(\"example_bound\", \"An example interaction bound\", Colors.MEDIUM_PURPLE, \"mdi:square-rounded\")\nclass ExampleBoundEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val criteria: List<Criteria> = emptyList(),\n override val modifiers: List<Modifier> = emptyList(),\n override val triggers: List<Ref<TriggerableEntry>> = emptyList(),\n) : InteractionBoundEntry {\n override fun build(player: Player): InteractionBound =\n ExampleBound(player, priority)\n}\n\nclass ExampleBound(\n private val player: Player,\n override val priority: Int\n) : ListenerInteractionBound {\n\n override fun initialize() {\n super.initialize()\n // Setup initial state\n }\n\n @EventHandler(priority = EventPriority.HIGHEST)\n private fun onPlayerAction(event: SomeCancellablePlayerEvent) {\n if (event.player.uniqueId != player.uniqueId) return\n\n if (boundConditionBroken()) {\n // For PlayerEvents, we have a handy method to handle the breaking\n handleEvent(event)\n\n // A manual version of the above\n when (event.player.boundState) {\n InteractionBoundState.BLOCKING -> event.isCancelled = true\n InteractionBoundState.INTERRUPTING -> InteractionEndTrigger.triggerFor(event.player, context())\n InteractionBoundState.IGNORING -> {}\n }\n }\n }\n\n private fun boundConditionBroken(): Boolean {\n // Check if the bound condition is broken\n return false\n }\n\n override fun teardown() {\n // Cleanup any state\n super.teardown()\n }\n}"
"content": "@Entry(\"example_bound\", \"An example interaction bound\", Colors.MEDIUM_PURPLE, \"mdi:square-rounded\")\nclass ExampleBoundEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val criteria: List<Criteria> = emptyList(),\n override val modifiers: List<Modifier> = emptyList(),\n override val triggers: List<Ref<TriggerableEntry>> = emptyList(),\n) : InteractionBoundEntry {\n override fun build(player: Player): InteractionBound =\n ExampleBound(player, priority)\n}\n\nclass ExampleBound(\n private val player: Player,\n override val priority: Int\n) : ListenerInteractionBound {\n\n override suspend fun initialize() {\n super.initialize()\n // Setup initial state\n }\n\n @EventHandler(priority = EventPriority.HIGHEST)\n private fun onPlayerAction(event: SomeCancellablePlayerEvent) {\n if (event.player.uniqueId != player.uniqueId) return\n\n if (boundConditionBroken()) {\n // For PlayerEvents, we have a handy method to handle the breaking\n handleEvent(event)\n\n // A manual version of the above\n when (event.player.boundState) {\n InteractionBoundState.BLOCKING -> event.isCancelled = true\n InteractionBoundState.INTERRUPTING -> InteractionEndTrigger.triggerFor(event.player, context())\n InteractionBoundState.IGNORING -> {}\n }\n }\n }\n\n private fun boundConditionBroken(): Boolean {\n // Check if the bound condition is broken\n return false\n }\n\n override suspend fun tick() {\n // Do something every tick\n }\n\n override suspend fun teardown() {\n // Cleanup any state\n super.teardown()\n }\n}"
},
"audience_entry": {
"file": "src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceEntry.kt",
"content": "@Entry(\"example_audience\", \"An example audience entry.\", Colors.GREEN, \"material-symbols:chat-rounded\")\nclass ExampleAudienceEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n) : AudienceEntry {\n override fun display(): AudienceDisplay {\n return ExampleAudienceDisplay()\n }\n}"
"content": "@Entry(\"example_audience\", \"An example audience entry.\", Colors.GREEN, \"material-symbols:chat-rounded\")\nclass ExampleAudienceEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n) : AudienceEntry {\n override suspend fun display(): AudienceDisplay {\n return ExampleAudienceDisplay()\n }\n}"
},
"audience_display": {
"file": "src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceEntry.kt",
Expand All @@ -111,9 +111,13 @@
"file": "src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceEntry.kt",
"content": "class AudienceDisplayWithEvents : AudienceDisplay() {\n override fun onPlayerAdd(player: Player) {}\n override fun onPlayerRemove(player: Player) {}\n\n // highlight-start\n @EventHandler\n fun onSomeEvent(event: SomeBukkitEvent) {\n // Do something when the event is triggered\n // This will trigger for all players, not just the ones in the audience.\n // So we need to check if the player is in the audience.\n if (event.player in this) {\n // Do something with the player\n }\n }\n // highlight-end\n}"
},
"check_player_in_audience": {
"file": "src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceEntry.kt",
"content": " if (player.inAudience(ref)) {\n // Do something with the player\n }"
},
"audience_filter_entry": {
"file": "src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceFilter.kt",
"content": "@Entry(\"example_audience_filter\", \"An example audience filter.\", Colors.MYRTLE_GREEN, \"material-symbols:filter-alt\")\nclass ExampleAudienceFilterEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val children: List<Ref<out AudienceEntry>> = emptyList(),\n) : AudienceFilterEntry {\n override fun display(): AudienceFilter = ExampleAudienceFilter(ref())\n}"
"content": "@Entry(\"example_audience_filter\", \"An example audience filter.\", Colors.MYRTLE_GREEN, \"material-symbols:filter-alt\")\nclass ExampleAudienceFilterEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val children: List<Ref<out AudienceEntry>> = emptyList(),\n) : AudienceFilterEntry {\n override suspend fun display(): AudienceFilter = ExampleAudienceFilter(ref())\n}"
},
"audience_filter": {
"file": "src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceFilter.kt",
Expand All @@ -125,11 +129,11 @@
},
"audience_filter_invertable": {
"file": "src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceFilter.kt",
"content": "@Entry(\"inverted_example_audience_filter\", \"An example audience filter.\", Colors.MYRTLE_GREEN, \"material-symbols:filter-alt\")\nclass InvertedExampleAudienceFilterEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val children: List<Ref<out AudienceEntry>> = emptyList(),\n // highlight-green\n override val inverted: Boolean = true,\n // highlight-next-line\n) : AudienceFilterEntry, Invertible {\n override fun display(): AudienceFilter = ExampleAudienceFilter(ref())\n}"
"content": "@Entry(\"inverted_example_audience_filter\", \"An example audience filter.\", Colors.MYRTLE_GREEN, \"material-symbols:filter-alt\")\nclass InvertedExampleAudienceFilterEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val children: List<Ref<out AudienceEntry>> = emptyList(),\n // highlight-green\n override val inverted: Boolean = true,\n // highlight-next-line\n) : AudienceFilterEntry, Invertible {\n override suspend fun display(): AudienceFilter = ExampleAudienceFilter(ref())\n}"
},
"single_filter_basic": {
"file": "src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceSingleFilter.kt",
"content": "@Entry(\"example_single_filter\", \"An example single filter entry\", Colors.MYRTLE_GREEN, \"material-symbols:filter-alt\")\nclass ExampleSingleFilterEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val priorityOverride: Optional<Int> = Optional.empty(),\n) : AudienceFilterEntry, PriorityEntry {\n override val children: List<Ref<out AudienceEntry>> get() = emptyList()\n\n override fun display(): AudienceFilter = ExampleSingleFilter(ref()) { player ->\n PlayerExampleDisplay(player, ExampleSingleFilter::class, ref())\n }\n}\n\nprivate class ExampleSingleFilter(\n ref: Ref<ExampleSingleFilterEntry>,\n createDisplay: (Player) -> PlayerExampleDisplay,\n) : SingleFilter<ExampleSingleFilterEntry, PlayerExampleDisplay>(ref, createDisplay) {\n // highlight-start\n // This must be a references to a shared map.\n // It CANNOT cache the map itself.\n override val displays: MutableMap<UUID, PlayerExampleDisplay>\n get() = map\n // highlight-end\n\n // highlight-start\n companion object {\n // This map is shared between all instances of the filter.\n private val map = ConcurrentHashMap<UUID, PlayerExampleDisplay>()\n }\n // highlight-end\n}\n\nprivate class PlayerExampleDisplay(\n player: Player,\n displayKClass: KClass<out SingleFilter<ExampleSingleFilterEntry, *>>,\n current: Ref<ExampleSingleFilterEntry>\n) : PlayerSingleDisplay<ExampleSingleFilterEntry>(player, displayKClass, current) {\n override fun setup() {\n super.setup()\n player.sendMessage(\"Display activated!\")\n }\n\n override fun tearDown() {\n super.tearDown()\n player.sendMessage(\"Display deactivated!\")\n }\n}"
"content": "@Entry(\"example_single_filter\", \"An example single filter entry\", Colors.MYRTLE_GREEN, \"material-symbols:filter-alt\")\nclass ExampleSingleFilterEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val priorityOverride: Optional<Int> = Optional.empty(),\n) : AudienceFilterEntry, PriorityEntry {\n override val children: List<Ref<out AudienceEntry>> get() = emptyList()\n\n override suspend fun display(): AudienceFilter = ExampleSingleFilter(ref()) { player ->\n PlayerExampleDisplay(player, ExampleSingleFilter::class, ref())\n }\n}\n\nprivate class ExampleSingleFilter(\n ref: Ref<ExampleSingleFilterEntry>,\n createDisplay: (Player) -> PlayerExampleDisplay,\n) : SingleFilter<ExampleSingleFilterEntry, PlayerExampleDisplay>(ref, createDisplay) {\n // highlight-start\n // This must be a references to a shared map.\n // It CANNOT cache the map itself.\n override val displays: MutableMap<UUID, PlayerExampleDisplay>\n get() = map\n // highlight-end\n\n // highlight-start\n companion object {\n // This map is shared between all instances of the filter.\n private val map = ConcurrentHashMap<UUID, PlayerExampleDisplay>()\n }\n // highlight-end\n}\n\nprivate class PlayerExampleDisplay(\n player: Player,\n displayKClass: KClass<out SingleFilter<ExampleSingleFilterEntry, *>>,\n current: Ref<ExampleSingleFilterEntry>\n) : PlayerSingleDisplay<ExampleSingleFilterEntry>(player, displayKClass, current) {\n override fun setup() {\n super.setup()\n player.sendMessage(\"Display activated!\")\n }\n\n override fun tearDown() {\n super.tearDown()\n player.sendMessage(\"Display deactivated!\")\n }\n}"
},
"single_filter_lifecycle": {
"file": "src/main/kotlin/com/typewritermc/example/entries/manifest/ExampleAudienceSingleFilter.kt",
Expand Down Expand Up @@ -209,6 +213,6 @@
},
"event_entry_with_context_keys": {
"file": "src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleEventEntry.kt",
"content": "@Entry(\"example_event_with_context_keys\", \"An example event entry with context keys.\", Colors.YELLOW, \"material-symbols:bigtop-updates\")\n// This tells Typewriter that this entry exposes some context\n@ContextKeys(ExampleContextKeys::class)\nclass ExampleEventEntryWithContextKeys(\n override val id: String = \"\",\n override val name: String = \"\",\n override val triggers: List<Ref<TriggerableEntry>> = emptyList(),\n) : EventEntry\n\nenum class ExampleContextKeys(override val klass: KClass<*>) : EntryContextKey {\n // The two `String::class` have to be the same.\n // The @KeyType is for the panel to know\n @KeyType(String::class)\n // The type here is for casting during runtime\n TEXT(String::class),\n\n @KeyType(Int::class)\n NUMBER(Int::class),\n\n // More complex types are also allowed.\n @KeyType(Position::class)\n POSITION(Position::class)\n}\n\n@EntryListener(ExampleEventEntryWithContextKeys::class)\nfun onEventAddContext(event: SomeBukkitEvent, query: Query<ExampleEventEntryWithContextKeys>) {\n val entries = query.find()\n entries.triggerAllFor(event.player) {\n // Make sure these values are drawn from the event.\n // You MUST supply all the context keys.\n ExampleContextKeys.TEXT withValue \"Hello World\"\n ExampleContextKeys.NUMBER withValue 42\n ExampleContextKeys.POSITION withValue Position.ORIGIN\n }\n}"
"content": "@Entry(\"example_event_with_context_keys\", \"An example event entry with context keys.\", Colors.YELLOW, \"material-symbols:bigtop-updates\")\n// This tells Typewriter that this entry exposes some context\n// highlight-next-line\n@ContextKeys(ExampleContextKeys::class)\nclass ExampleEventEntryWithContextKeys(\n override val id: String = \"\",\n override val name: String = \"\",\n override val triggers: List<Ref<TriggerableEntry>> = emptyList(),\n) : EventEntry\n\n// highlight-start\nenum class ExampleContextKeys(override val klass: KClass<*>) : EntryContextKey {\n // The two `String::class` have to be the same.\n // The @KeyType is for the panel to know\n @KeyType(String::class)\n // The type here is for casting during runtime\n TEXT(String::class),\n\n @KeyType(Int::class)\n NUMBER(Int::class),\n\n // More complex types are also allowed.\n @KeyType(Position::class)\n POSITION(Position::class)\n}\n// highlight-end\n\n@EntryListener(ExampleEventEntryWithContextKeys::class)\nfun onEventAddContext(event: SomeBukkitEvent, query: Query<ExampleEventEntryWithContextKeys>) {\n val entries = query.find()\n // highlight-start\n entries.triggerAllFor(event.player) {\n // Make sure these values are drawn from the event.\n // You MUST supply all the context keys.\n ExampleContextKeys.TEXT withValue \"Hello World\"\n ExampleContextKeys.NUMBER withValue 42\n ExampleContextKeys.POSITION withValue Position.ORIGIN\n }\n // highlight-end\n}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface SharedAdvancedEntityInstance : EntityInstanceEntry {
@WithRotation
val spawnLocation: Position

override fun display(): AudienceFilter {
override suspend fun display(): AudienceFilter {
val activityCreator = this.activity.get() ?: IdleActivity
val (definition, suppliers) = baseInfo() ?: return PassThroughFilter(ref())

Expand All @@ -42,7 +42,7 @@ interface GroupAdvancedEntityInstance : EntityInstanceEntry {
@Help("The group that this entity instance belongs to.")
val group: Ref<out GroupEntry>

override fun display(): AudienceFilter {
override suspend fun display(): AudienceFilter {
val activityCreator = this.activity.get() ?: IdleActivity

val group = this.group.get() ?: throw IllegalStateException("No group found for the group entity instance.")
Expand All @@ -59,7 +59,7 @@ interface IndividualAdvancedEntityInstance : EntityInstanceEntry {
@WithRotation
val spawnLocation: Var<Position>

override fun display(): AudienceFilter {
override suspend fun display(): AudienceFilter {
val activityCreator = this.activity.get() ?: IdleActivity

val (definition, suppliers) = baseInfo() ?: return PassThroughFilter(ref())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface SimpleEntityInstance : EntityInstanceEntry {
override val children: List<Ref<out AudienceEntry>>
get() = data

override fun display(): AudienceFilter {
override suspend fun display(): AudienceFilter {
val definition = definition.get().logErrorIfNull("You must specify a definition for $name")
?: return PassThroughFilter(ref())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ annotation class ChildOnly

@Tags("audience")
interface AudienceEntry : ManifestEntry, PlaceholderEntry {
fun display(): AudienceDisplay
suspend fun display(): AudienceDisplay

override fun parser() = placeholderParser {
literal("players") {
Expand All @@ -43,7 +43,7 @@ interface AudienceEntry : ManifestEntry, PlaceholderEntry {
@Tags("audience_filter")
interface AudienceFilterEntry : AudienceEntry {
val children: List<Ref<out AudienceEntry>>
override fun display(): AudienceFilter
override suspend fun display(): AudienceFilter
}

interface Invertible {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface PropertyCollector<P : EntityProperty> {
@Tags("entity_data")
interface EntityData<P : EntityProperty> : AudienceEntry, PropertySupplier<P>, PriorityEntry {
override fun canApply(player: Player): Boolean = player.inAudience(this)
override fun display(): AudienceDisplay = PassThroughDisplay()
override suspend fun display(): AudienceDisplay = PassThroughDisplay()
}

@Tags("generic_entity_data")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ interface SidebarEntry : AudienceFilterEntry, PlaceholderEntry, PriorityEntry {
supply { player -> display(player) }
}

override fun display(): AudienceFilter = SidebarFilter(ref()) { player ->
override suspend fun display(): AudienceFilter = SidebarFilter(ref()) { player ->
PlayerSidebarDisplay(player, SidebarFilter::class, ref())
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class BossBarEntry(
@Help("Any flags to apply to the boss bar")
val flags: List<BossBar.Flag> = emptyList(),
) : AudienceEntry {
override fun display(): AudienceDisplay {
override suspend fun display(): AudienceDisplay {
return BossBarDisplay(title, progress, color, style, flags)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class CinematicAudienceEntry(
val pageId: String = "",
override val inverted: Boolean = false
) : AudienceFilterEntry, Invertible {
override fun display(): AudienceFilter = CinematicAudienceFilter(
override suspend fun display(): AudienceFilter = CinematicAudienceFilter(
ref(),
pageId,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class CinematicSkippableAudienceEntry(
override val children: List<Ref<out AudienceEntry>> = emptyList(),
override val inverted: Boolean = false,
) : AudienceFilterEntry, Invertible, PlaceholderEntry {
override fun display(): AudienceFilter {
override suspend fun display(): AudienceFilter {
return CinematicSkippableAudienceDisplay(ref())
}

Expand Down
Loading

0 comments on commit 9d64b9b

Please sign in to comment.