Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ dependencies {
testImplementation("com.willfp:eco:${findProperty("eco-version")}")
testImplementation(kotlin("stdlib", version = "2.3.0"))

// eco's placeholder types reference Bukkit classes, which the server provides at runtime.
testImplementation("org.purpurmc.purpur:purpur-api:1.21.4-R0.1-SNAPSHOT")

// Reads the bundled holidays.yml in tests; the server provides SnakeYAML at runtime.
testImplementation("org.yaml:snakeyaml:2.2")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.willfp.libreforge

import com.willfp.eco.core.registry.KRegistrable
import com.willfp.eco.core.registry.Registry
import java.util.concurrent.ConcurrentHashMap

interface Aliased : KRegistrable {
/**
Expand All @@ -16,7 +17,7 @@ interface Aliased : KRegistrable {
* When an ID and an alias clash, the most recently registered element wins.
*/
abstract class AliasedRegistry<T : Aliased> : Registry<T>() {
private val aliasRegistry = mutableMapOf<String, T>()
private val aliasRegistry = ConcurrentHashMap<String, T>()

override fun get(id: String): T? {
return aliasRegistry[id] ?: super.get(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ interface BlockBreaker {
/**
* Break [blocks] on behalf of this dispatcher.
*
* Called on the thread that owns the blocks: on Folia, blocks in other regions are left out.
*
* @param data The trigger the breaking effect is running for, which carries
* the drop event the drops belong in, where one exists.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import com.willfp.libreforge.slot.ItemHolderFinder
import org.bukkit.Location
import org.bukkit.event.Event
import org.bukkit.event.HandlerList
import java.util.Collections
import java.util.IdentityHashMap
import java.util.UUID
import java.util.concurrent.CopyOnWriteArrayList


/**
Expand Down Expand Up @@ -178,12 +180,14 @@ data class ProvidedEffectBlock(
}
}

private val providers = mutableListOf<HolderProvider>()
private val providers = CopyOnWriteArrayList<HolderProvider>()

private val providerIds = IdentityHashMap<HolderProvider, String>()
private val providerIds: MutableMap<HolderProvider, String> = Collections.synchronizedMap(IdentityHashMap())

private val providerIdCounts = mutableMapOf<String, Int>()

private val providerRegistrationLock = Any()

/**
* The registered providers, in registration order.
*/
Expand All @@ -200,16 +204,19 @@ internal fun registeredProviderId(provider: HolderProvider): String =
* Register a new holder provider.
*/
fun registerHolderProvider(provider: HolderProvider): Boolean {
if (providerIds.containsKey(provider)) {
return false
}
synchronized(providerRegistrationLock) {
if (providerIds.containsKey(provider)) {
return false
}

val baseId = provider.id
val count = providerIdCounts.getOrDefault(baseId, 0)
providerIdCounts[baseId] = count + 1
providerIds[provider] = if (count == 0) baseId else "$baseId#$count"
val baseId = provider.id
val count = providerIdCounts.getOrDefault(baseId, 0)
providerIdCounts[baseId] = count + 1
providerIds[provider] = if (count == 0) baseId else "$baseId#$count"

providers.add(provider)
}

providers.add(provider)
HolderSignals.registerProvider(provider)
HolderStates.markProviderEverywhere(provider)
return true
Expand All @@ -219,10 +226,12 @@ fun registerHolderProvider(provider: HolderProvider): Boolean {
* Remove holder providers, used when the plugin that registered them is disabled.
*/
internal fun unregisterHolderProviders(filter: (HolderProvider) -> Boolean): List<HolderProvider> {
val removed = providers.filter(filter)
providers.removeAll(removed)
removed.forEach { providerIds.remove(it) }
return removed
synchronized(providerRegistrationLock) {
val removed = providers.filter(filter)
providers.removeAll(removed)
removed.forEach { providerIds.remove(it) }
return removed
}
}

/**
Expand Down Expand Up @@ -312,7 +321,7 @@ inline fun <reified T> registerSpecificHolderProvider(
fun registerSlotHolderFinderAsProvider(finder: ItemHolderFinder<*>) =
registerHolderProvider(finder.toHolderProvider())

private val refreshFunctions = mutableListOf<(Dispatcher<*>) -> Unit>()
private val refreshFunctions = CopyOnWriteArrayList<(Dispatcher<*>) -> Unit>()

/**
* Register a function to be called before a dispatcher's providers are re-asked.
Expand Down Expand Up @@ -376,7 +385,7 @@ fun Dispatcher<*>.refreshHolders() =
fun Dispatcher<*>.forceRefreshHolders() =
HolderStates.forceMarkAllProviders(this)

private val holderPlaceholderProviders = mutableListOf<(ProvidedHolder, Dispatcher<*>) -> Collection<NamedValue>>()
private val holderPlaceholderProviders = CopyOnWriteArrayList<(ProvidedHolder, Dispatcher<*>) -> Collection<NamedValue>>()

/**
* Register a function to generate placeholders for a holder.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,26 @@ import org.bukkit.event.Listener
import org.bukkit.plugin.EventExecutor
import java.util.Collections
import java.util.IdentityHashMap
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.CopyOnWriteArraySet

/**
* Routes change signals to the providers and conditions that declared them.
*/
internal object HolderSignals {
private val providersBySignal = HashMap<HolderChange, MutableList<HolderProvider>>()
private val providersBySignal = ConcurrentHashMap<HolderChange, CopyOnWriteArrayList<HolderProvider>>()

private val conditionsBySignal = HashMap<HolderChange, MutableSet<Condition<*>>>()
private val conditionsBySignal = ConcurrentHashMap<HolderChange, CopyOnWriteArraySet<Condition<*>>>()

private class CustomHandler(
val owner: Any,
val action: (Event) -> Unit
)

private val customHandlers = HashMap<Class<out Event>, MutableList<CustomHandler>>()
private val customHandlers = ConcurrentHashMap<Class<out Event>, CopyOnWriteArrayList<CustomHandler>>()

private val listening = mutableSetOf<Class<out Event>>()
private val listening = ConcurrentHashMap.newKeySet<Class<out Event>>()

private val registeredConditions: MutableSet<Condition<*>> = Collections.newSetFromMap(IdentityHashMap())

Expand All @@ -36,16 +39,18 @@ internal object HolderSignals {
fun conditionsFor(change: HolderChange): Set<Condition<*>> =
conditionsBySignal[change] ?: emptySet()

@Synchronized
fun registerProvider(provider: HolderProvider) {
for (change in provider.invalidatedBy) {
if (change is HolderChange.Custom<*>) {
addCustom(change, provider) { dispatcher, scope -> HolderStates.markProvider(dispatcher, provider, scope) }
} else {
providersBySignal.getOrPut(change) { mutableListOf() } += provider
providersBySignal.computeIfAbsent(change) { CopyOnWriteArrayList() } += provider
}
}
}

@Synchronized
fun registerCondition(condition: Condition<*>) {
// Registration re-runs on every reload.
if (!registeredConditions.add(condition)) {
Expand All @@ -58,14 +63,15 @@ internal object HolderSignals {
if (change is HolderChange.Custom<*>) {
addCustom(change, condition) { dispatcher, scope -> HolderStates.markCondition(dispatcher, condition, scope) }
} else {
conditionsBySignal.getOrPut(change) { mutableSetOf() } += condition
conditionsBySignal.computeIfAbsent(change) { CopyOnWriteArraySet() } += condition
}
}
}

/**
* Stop routing signals to [providers] and to the conditions loaded by [classLoader].
*/
@Synchronized
fun unregisterOwnedBy(classLoader: ClassLoader, providers: Collection<HolderProvider>) {
val removed: MutableSet<Any> = Collections.newSetFromMap(IdentityHashMap())
removed.addAll(providers)
Expand All @@ -92,7 +98,7 @@ internal object HolderSignals {
@Suppress("UNCHECKED_CAST")
val scopeOf = change.scopeOf as ((Event) -> SignalScope?)?

customHandlers.getOrPut(change.event) { mutableListOf() } += CustomHandler(owner) { event ->
customHandlers.computeIfAbsent(change.event) { CopyOnWriteArrayList() } += CustomHandler(owner) { event ->
dispatcherOf(event)?.let { mark(it, scopeOf?.invoke(event)) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ internal enum class StateKind {
}

/**
* Everything libreforge tracks for one dispatcher. Accessed only on the main thread.
* Everything libreforge tracks for one dispatcher. Accessed only on the thread that owns the
* dispatcher (the main thread off Folia); [admitted] and [isRemoved] are also read by the engine.
*/
internal class HolderState(
val dispatcher: Dispatcher<*>,
Expand Down Expand Up @@ -261,13 +262,15 @@ internal class HolderState(
private var clickScopes: ArrayList<SignalScope>? = ArrayList()

// Set when the state is dropped, possibly by a handler in the middle of its own update.
@Volatile
var isRemoved = false

var admissionAttempts = 0

var bypassCooldown = false

// Non-player states wait for the new-state budget before their first flush.
@Volatile
var admitted = false
var needsCleanup = false

Expand Down
Loading
Loading