Skip to content

Commit

Permalink
Merge branch 'next' of https://github.com/age-series/libage into next
Browse files Browse the repository at this point in the history
  • Loading branch information
Grissess committed Dec 18, 2023
2 parents a53721c + 7e78fd5 commit ad78115
Show file tree
Hide file tree
Showing 32 changed files with 6,877 additions and 1,496 deletions.
8 changes: 8 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
testImplementation("org.assertj", "assertj-core", "3.22.0")
testImplementation("org.junit.jupiter", "junit-jupiter-api", "5.8.2")
testRuntimeOnly("org.junit.jupiter", "junit-jupiter-engine", "5.8.2")
implementation(kotlin("reflect"))
}

tasks {
Expand Down Expand Up @@ -94,13 +95,20 @@ tasks {
// By default, build everything, put it somewhere convenient, and run the tests.
defaultTasks = mutableListOf("build", "test")

val compileArgs = listOf(
"-Xopt-in=kotlin.RequiresOptIn"
)

val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "8"
freeCompilerArgs += compileArgs
}

val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "8"
freeCompilerArgs += compileArgs
}

java {
Expand Down
39 changes: 33 additions & 6 deletions src/main/kotlin/org/ageseries/libage/data/BiMap.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,22 @@ interface MutableBiMap<F, B>: BiMap<F, B> {
* the guarantees above.
*/
/**
* Add a bijection from [f] to [b], returning true iff some bijection had to be replaced.
* Add a bijection from [f] to [b], returning true if some bijection had to be replaced.
*/
fun addOrReplace(f: F, b: B): Boolean

/**
* Add a bijection from [f] to [b]. If this replaces some other bijection, raise an error.
*/
fun add(f: F, b: B) {
if(forward[f] != null || backward[b] != null) error("would replace a bijection")
addOrReplace(f, b) // assert not this
val existingF = forward[f]
val existingB = backward[b]

if(existingF != null || existingB != null) {
error("Associating $f and $b would replace $existingF $existingB")
}

addOrReplace(f, b)
}

/**
Expand Down Expand Up @@ -94,6 +100,20 @@ interface MutableBiMap<F, B>: BiMap<F, B> {
}
}

/**
* Bijectively associates the keys with values selected using [valueSelector].
* Similar to Kotlin's [associateWith].
* */
inline fun <K, V> Iterable<K>.associateWithBi(valueSelector: (K) -> V): BiMap<K, V> {
val result = mutableBiMapOf<K, V>()

this.forEach { k ->
result.add(k, valueSelector(k))
}

return result
}

/**
* An implementation of [MutableBiMap] using a pair of Kotlin's standard maps.
*/
Expand Down Expand Up @@ -139,17 +159,24 @@ class MutableMapPairBiMap<F, B>(pairs: Iterator<Pair<F, B>>): MutableBiMap<F, B>
forward.clear()
backward.clear()
}

override fun toString() = buildString {
forward.forEach { (f, b) ->
this.appendLine("$f <-> $b")
}
}
}

/**
* An immutable view of a realized [MutableBiMap].
*/
@JvmInline
value class ImmutableBiMapView<F, B>(val inner: MutableBiMap<F, B>): BiMap<F, B> {
value class ImmutableBiMapView<F, B>(private val inner: MutableBiMap<F, B>): BiMap<F, B> {
override val forward: Map<F, B>
inline get() = inner.forward
get() = inner.forward

override val backward: Map<B, F>
inline get() = inner.backward
get() = inner.backward
}

/**
Expand Down
92 changes: 59 additions & 33 deletions src/main/kotlin/org/ageseries/libage/data/DisjointSet.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
package org.ageseries.libage.data

/**
* A class for using the Disjoint Sets "Union-Find" algorithm.
* Implementation of the Disjoint-Set data structure designed for use in conjunction with the Union-Find algorithm.
* It is intended to be extended by inheritors, where the term "Super" (akin to a **superclass**) clarifies its nature.
* To enhance usability, a *self-type parameter* ([Self]) has been integrated.
*
* There are two ways to use this:
* The [representative] can be retrieved as [Self], and the [union] operation readily accepts [Self].
* Essentially, inheritors can work with instances of their class without necessitating casting (unlike the old implementation).
*
* - Inherit from it, using the methods on the object itself;
* - compose it into your class; or
* - do both.
*
* Typical use case is as follows:
*
* 1. Choose your method above; for the former, the DisjointSet object is `this`; for the latter, substitute your field.
* 2. Unify DisjointSets by calling [unite].
* 3. Determine if two DisjointSets are in the same component by testing their [representative] for equality.
* 4. Optionally, mutate the data on a DisjointSet subclass' representative, knowing that the mutations are visible from all DisjointSets with the same representative.
*
* Note that it is difficult to control which instance will ultimately *be* the representative; in the cases where it can't be avoided, [priority] can be used, but this is advisable only as a last resort (since it reduces the optimality of this algorithm).
* It's important to note that controlling which instance ultimately becomes the representative can be challenging.
* In cases where this is unavoidable, the [priority] parameter can be employed.
* However, it's advisable to use this option sparingly, as it compromises the optimality of the algorithm and should only be considered as a last resort.
*/
open class DisjointSet {

@Suppress("UNCHECKED_CAST") // Side effect of self parameter. Fine in our case.
abstract class SuperDisjointSet<Self : SuperDisjointSet<Self>> {
/**
* The size of this tree; loosely, how many Sets have been merged, including transitively, with this one.
*
Expand All @@ -32,7 +26,8 @@ open class DisjointSet {
*
* Following this recursively will lead to the [representative]. All representatives refer to themselves as their parent.
*/
var parent: DisjointSet = this
@Suppress("LeakingThis") // Fine in this case.
var parent: Self = this as Self

/**
* The priority of the merge. If set, this overrides the "merge by size" algorithm in [unite].
Expand All @@ -48,34 +43,65 @@ open class DisjointSet {
*
* This is implemented using the "Path splitting" algorithm.
*/
val representative: DisjointSet
val representative: Self
get() {
var cur = this
while (cur.parent != cur) {
val next = cur.parent
cur.parent = next.parent
cur = next
var current = this

while (current.parent != current) {
val next = current.parent
current.parent = next.parent
current = next
}
return cur

return current as Self
}

/**
* Unite this instance with another instance of DisjointSet.
* *Union operation*.
*
* After this is done, both this and [other] will have the same [representative] as each other (and as all other Sets with which they were previously united).
*
* This is implemented using the "by size" merge algorithm, adjusted for [priority].
*/
open fun unite(other: DisjointSet) {
val trep = representative
val orep = other.representative
if (trep == orep) return
val (bigger, smaller) = when {
trep.priority > orep.priority -> Pair(trep, orep)
orep.priority > trep.priority -> Pair(orep, trep)
else -> if (trep.size < orep.size) Pair(orep, trep) else Pair(trep, orep)
open fun unite(other: Self) {
val thisRep = representative
val otherRep = other.representative

if (thisRep == otherRep){
return
}

val bigger: Self
val smaller: Self

// Override with priority:
if(thisRep.priority > otherRep.priority) {
bigger = thisRep
smaller = otherRep
}
else if(otherRep.priority > thisRep.priority) {
bigger = otherRep
smaller = thisRep
}
else {
// By size:
if (thisRep.size < otherRep.size) {
bigger = otherRep
smaller = thisRep
} else {
bigger = thisRep
smaller = otherRep
}
}

smaller.parent = bigger.parent
bigger.size += smaller.size
}
}

/**
* Composable disjoint set, implemented as a [SuperDisjointSet].
* This implementation proves beneficial in specific algorithms where certain elements are partitioned using union-find.
* These elements might represent pure data or simply may not want or need to implement [SuperDisjointSet].
* */
class DisjointSet(override var priority: Int = 0) : SuperDisjointSet<DisjointSet>() // *It also proves useful in unit tests.
118 changes: 118 additions & 0 deletions src/main/kotlin/org/ageseries/libage/data/Events.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.ageseries.libage.data

import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.reflect.KClass

/**
* Producer of events which are distributed to [EventHandler]s.
* */
interface EventSource {
/**
* Adds a [handler] for the event [eventClass].
* */
fun registerHandler(eventClass: KClass<*>, handler: EventHandler<Event>)

/**
* Removes [handler] for the event [eventClass].
* */
fun unregisterHandler(eventClass: KClass<*>, handler: EventHandler<Event>)
}

/**
* Dispatcher for events of different types.
* */
interface EventDispatcher {
/**
* Sends the [event] to all handlers capable of handling it.
* @return The number of handlers which received the event.
* */
fun send(event: Event): Int
}

/**
* The Event Bus oversees a collection of event handlers.
* When an event is dispatched, it is relayed to all handlers capable of managing that specific event type.
* Currently, event polymorphism is not implemented so a handler must be registered with the exact class of the event.
* The system is designed with thread safety in mind.
* @param allowList An optional whitelist of event classes. If a handler for an event which is not allowed is registered, an error is raised. Same goes for events that are sent.
* */
class EventBus(private val allowList: Set<KClass<*>>? = null) : EventSource, EventDispatcher {
private val handlers = ConcurrentHashMap<KClass<*>, CopyOnWriteArrayList<EventHandler<Event>>>()

private fun validateEvent(eventClass: KClass<*>) {
if (allowList != null) {
check(allowList.contains(eventClass)) {
"The event manager prohibits $eventClass"
}
}

}

override fun registerHandler(eventClass: KClass<*>, handler: EventHandler<Event>) {
validateEvent(eventClass)

val handlers = handlers.computeIfAbsent(eventClass) {
CopyOnWriteArrayList()
}

handlers.add(handler)
}

override fun unregisterHandler(eventClass: KClass<*>, handler: EventHandler<Event>) {
validateEvent(eventClass)

val handlers = handlers[eventClass]
?: error("Could not find handlers for $eventClass")

if (!handlers.remove(handler)) {
error("Could not remove handler $handler")
}
}

/**
* Sends an event to all subscribed listeners.
* */
override fun send(event: Event): Int {
validateEvent(event.javaClass.kotlin)

val listeners = this.handlers[event::class]
?: return 0

var visited = 0
listeners.forEach {
it.handle(event)
++visited
}

return visited
}
}

/**
* Registers an event handler for events of type [TEvent].
* */
@Suppress("UNCHECKED_CAST")
inline fun <reified TEvent : Event> EventSource.registerHandler(handler: EventHandler<TEvent>) {
registerHandler(TEvent::class, handler as EventHandler<Event>)
}

/**
* Removes an event handler for events of type [TEvent].
* */
@Suppress("UNCHECKED_CAST")
inline fun <reified TEvent : Event> EventSource.unregisterHandler(handler: EventHandler<TEvent>) {
unregisterHandler(TEvent::class, handler as EventHandler<Event>)
}

/**
* Marker interface implemented by all events.
* */
interface Event

/**
* A handler for events of the specified type.
* */
fun interface EventHandler<T : Event> {
fun handle(event: T)
}
20 changes: 20 additions & 0 deletions src/main/kotlin/org/ageseries/libage/data/MultiMap.kt
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,23 @@ fun <K, V> Map<K, V>.toMultiMap(): MultiMap<K, V> = entries.map { it.toPair() }.
* Creates a MutableMultiMap from a Map, with each key associated to a set of size at most one.
*/
fun <K, V> Map<K, V>.toMutableMultiMap(): MutableMultiMap<K, V> = entries.map { it.toPair() }.toMutableMultiMap()

/**
* Constructs a [MultiMap] keyed by [K] with [T] values.
* Similar to [associateBy]
* */
inline fun <T, K> Iterable<T>.associateByMulti(keySelector: (T) -> K): MultiMap<K, T> {
val result = MutableSetMapMultiMap<K, T>()

this.forEach { value ->
result[keySelector(value)].add(value)
}

return result
}

inline fun <T, K> Array<T>.associateByMulti(keySelector: (T) -> K): MultiMap<K, T> {
val result = MutableSetMapMultiMap<K, T>()
this.forEach { value -> result[keySelector(value)].add(value) }
return result
}
Loading

0 comments on commit ad78115

Please sign in to comment.