diff --git a/gradle.properties b/gradle.properties index 5f93cb8..a780ad9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ kotlin.code.style=official specifyKotlinAsDependency=false projectGroup=ru.dbotthepony.kommons -projectVersion=2.0.0 +projectVersion=2.1.0 guavaDepVersion=33.0.0 gsonDepVersion=2.8.9 diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/io/VectorsIO.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/io/VectorsIO.kt index 8ed5354..4f58928 100644 --- a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/io/VectorsIO.kt +++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/io/VectorsIO.kt @@ -52,14 +52,26 @@ val Vector4iCodec = StreamCodec.Impl(DataInputStream::readVector4i, DataOutputSt val Vector4dCodec = StreamCodec.Impl(DataInputStream::readVector4d, DataOutputStream::writeStruct4d) val Vector4fCodec = StreamCodec.Impl(DataInputStream::readVector4f, DataOutputStream::writeStruct4f) -fun SynchedDelegates.vec2i(value: Vector2i, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, Vector2iCodec, getter, setter) -fun SynchedDelegates.vec2d(value: Vector2d, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, Vector2dCodec, getter, setter) -fun SynchedDelegates.vec2f(value: Vector2f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, Vector2fCodec, getter, setter) +fun DelegateSyncher.vec2i(value: Vector2i, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector2iCodec) +fun DelegateSyncher.vec2d(value: Vector2d, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector2dCodec) +fun DelegateSyncher.vec2f(value: Vector2f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector2fCodec) -fun SynchedDelegates.vec3i(value: Vector3i, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, Vector3iCodec, getter, setter) -fun SynchedDelegates.vec3d(value: Vector3d, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, Vector3dCodec, getter, setter) -fun SynchedDelegates.vec3f(value: Vector3f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, Vector3fCodec, getter, setter) +fun DelegateSyncher.vec3i(value: Vector3i, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector3iCodec) +fun DelegateSyncher.vec3d(value: Vector3d, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector3dCodec) +fun DelegateSyncher.vec3f(value: Vector3f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector3fCodec) -fun SynchedDelegates.vec4i(value: Vector4i, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, Vector4iCodec, getter, setter) -fun SynchedDelegates.vec4d(value: Vector4d, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, Vector4dCodec, getter, setter) -fun SynchedDelegates.vec4f(value: Vector4f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, Vector4fCodec, getter, setter) +fun DelegateSyncher.vec4i(value: Vector4i, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector4iCodec) +fun DelegateSyncher.vec4d(value: Vector4d, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector4dCodec) +fun DelegateSyncher.vec4f(value: Vector4f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector4fCodec) + +fun DelegateSyncher.Group.vec2i(value: Vector2i, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector2iCodec) +fun DelegateSyncher.Group.vec2d(value: Vector2d, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector2dCodec) +fun DelegateSyncher.Group.vec2f(value: Vector2f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector2fCodec) + +fun DelegateSyncher.Group.vec3i(value: Vector3i, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector3iCodec) +fun DelegateSyncher.Group.vec3d(value: Vector3d, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector3dCodec) +fun DelegateSyncher.Group.vec3f(value: Vector3f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector3fCodec) + +fun DelegateSyncher.Group.vec4i(value: Vector4i, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector4iCodec) +fun DelegateSyncher.Group.vec4d(value: Vector4d, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector4dCodec) +fun DelegateSyncher.Group.vec4f(value: Vector4f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector4fCodec) diff --git a/src/main/kotlin/ru/dbotthepony/kommons/collect/ProxiedMap.kt b/src/main/kotlin/ru/dbotthepony/kommons/collect/ListenableMap.kt similarity index 73% rename from src/main/kotlin/ru/dbotthepony/kommons/collect/ProxiedMap.kt rename to src/main/kotlin/ru/dbotthepony/kommons/collect/ListenableMap.kt index e2f4fc2..5d3eebd 100644 --- a/src/main/kotlin/ru/dbotthepony/kommons/collect/ProxiedMap.kt +++ b/src/main/kotlin/ru/dbotthepony/kommons/collect/ListenableMap.kt @@ -1,33 +1,73 @@ package ru.dbotthepony.kommons.collect -abstract class ProxiedMap(protected val backingMap: MutableMap = HashMap()) : MutableMap { - protected abstract fun onClear() - protected abstract fun onValueAdded(key: K, value: V) - protected abstract fun onValueRemoved(key: K, value: V) +import ru.dbotthepony.kommons.util.Listenable +import java.util.concurrent.CopyOnWriteArrayList - final override val size: Int +/** + * Proxies all operations to [backingMap] along with generating events for [MapListener]s + */ +open class ListenableMap(protected val backingMap: MutableMap = HashMap()) : MutableMap { + interface MapListener { + fun onClear() + fun onValueAdded(key: K, value: V) + fun onValueRemoved(key: K, value: V) + } + + protected open fun onClear() { + listeners.forEach { it.callback.onClear() } + } + + protected open fun onValueAdded(key: K, value: V) { + listeners.forEach { it.callback.onValueAdded(key, value) } + } + + protected open fun onValueRemoved(key: K, value: V) { + listeners.forEach { it.callback.onValueRemoved(key, value) } + } + + protected inner class Listener(val callback: MapListener) : Listenable.L { + init { + listeners.add(this) + } + + override fun remove() { + listeners.remove(this) + } + } + + protected val listeners = CopyOnWriteArrayList() + + fun addListener(listener: MapListener): Listenable.L { + return Listener(listener) + } + + fun clearListeners() { + listeners.clear() + } + + override val size: Int get() = backingMap.size - final override fun containsKey(key: K): Boolean { + override fun containsKey(key: K): Boolean { return backingMap.containsKey(key) } - final override fun containsValue(value: V): Boolean { + override fun containsValue(value: V): Boolean { return backingMap.containsValue(value) } - final override fun get(key: K): V? { + override fun get(key: K): V? { return backingMap[key] } - final override fun isEmpty(): Boolean { + override fun isEmpty(): Boolean { return backingMap.isEmpty() } - final override val entries: MutableSet> by lazy { + override val entries: MutableSet> by lazy { object : MutableSet> { override fun add(element: MutableMap.MutableEntry): Boolean { - this@ProxiedMap[element.key] = element.value + this@ListenableMap[element.key] = element.value return true } @@ -40,10 +80,10 @@ abstract class ProxiedMap(protected val backingMap: MutableMap = Has } override fun clear() { - this@ProxiedMap.clear() + this@ListenableMap.clear() } - private val setParent = this@ProxiedMap.backingMap.entries + private val setParent = this@ListenableMap.backingMap.entries override fun iterator(): MutableIterator> { return object : MutableIterator> { @@ -116,9 +156,9 @@ abstract class ProxiedMap(protected val backingMap: MutableMap = Has } } - final override val keys: MutableSet by lazy { + override val keys: MutableSet by lazy { object : MutableSet { - val parent = this@ProxiedMap.backingMap.keys + val parent = this@ListenableMap.backingMap.keys override fun add(element: K): Boolean { throw UnsupportedOperationException() @@ -129,7 +169,7 @@ abstract class ProxiedMap(protected val backingMap: MutableMap = Has } override fun clear() { - this@ProxiedMap.clear() + this@ListenableMap.clear() } override fun iterator(): MutableIterator { @@ -147,7 +187,7 @@ abstract class ProxiedMap(protected val backingMap: MutableMap = Has override fun remove() { val last = last ?: throw IllegalStateException("Never called next()") - val value = this@ProxiedMap[last] ?: throw ConcurrentModificationException() + val value = this@ListenableMap[last] ?: throw ConcurrentModificationException() parentIterator.remove() onValueRemoved(last, value) } @@ -155,7 +195,7 @@ abstract class ProxiedMap(protected val backingMap: MutableMap = Has } override fun remove(element: K): Boolean { - return this@ProxiedMap.remove(element) != null + return this@ListenableMap.remove(element) != null } override fun removeAll(elements: Collection): Boolean { @@ -199,9 +239,9 @@ abstract class ProxiedMap(protected val backingMap: MutableMap = Has } } - final override val values: MutableCollection by lazy { + override val values: MutableCollection by lazy { object : MutableCollection { - private val parent = this@ProxiedMap.backingMap.values + private val parent = this@ListenableMap.backingMap.values override val size: Int get() = parent.size @@ -227,7 +267,7 @@ abstract class ProxiedMap(protected val backingMap: MutableMap = Has } override fun clear() { - this@ProxiedMap.clear() + this@ListenableMap.clear() } override fun iterator(): MutableIterator { @@ -249,8 +289,8 @@ abstract class ProxiedMap(protected val backingMap: MutableMap = Has } override fun remove(element: V): Boolean { - val indexOf = this@ProxiedMap.backingMap.firstNotNullOfOrNull { if (it.value == element) it.key else null } ?: return false - this@ProxiedMap.remove(indexOf) + val indexOf = this@ListenableMap.backingMap.firstNotNullOfOrNull { if (it.value == element) it.key else null } ?: return false + this@ListenableMap.remove(indexOf) return true } @@ -296,7 +336,7 @@ abstract class ProxiedMap(protected val backingMap: MutableMap = Has return existing } - final override fun putAll(from: Map) { + override fun putAll(from: Map) { for ((k, v) in from) { this[k] = v } diff --git a/src/main/kotlin/ru/dbotthepony/kommons/collect/ListenableSet.kt b/src/main/kotlin/ru/dbotthepony/kommons/collect/ListenableSet.kt new file mode 100644 index 0000000..9f52af3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kommons/collect/ListenableSet.kt @@ -0,0 +1,141 @@ +package ru.dbotthepony.kommons.collect + +import ru.dbotthepony.kommons.util.Listenable +import java.util.concurrent.CopyOnWriteArrayList + +/** + * Proxies all operations to [backingSet] along with generating events for [SetListener]s + */ +open class ListenableSet(protected val backingSet: MutableSet) : MutableSet { + interface SetListener { + fun onClear() + fun onValueAdded(element: E) + fun onValueRemoved(element: E) + } + + protected open fun onClear() { + listeners.forEach { it.callback.onClear() } + } + + protected open fun onValueAdded(element: E) { + listeners.forEach { it.callback.onValueAdded(element) } + } + + protected open fun onValueRemoved(element: E) { + listeners.forEach { it.callback.onValueRemoved(element) } + } + + protected inner class Listener(val callback: SetListener) : Listenable.L { + init { + listeners.add(this) + } + + override fun remove() { + listeners.remove(this) + } + } + + protected val listeners = CopyOnWriteArrayList() + + fun addListener(listener: SetListener): Listenable.L { + return Listener(listener) + } + + fun clearListeners() { + listeners.clear() + } + + override fun add(element: E): Boolean { + if (backingSet.add(element)) { + onValueAdded(element) + return true + } + + return false + } + + override fun remove(element: E): Boolean { + if (backingSet.remove(element)) { + onValueRemoved(element) + return true + } + + return false + } + + override fun addAll(elements: Collection): Boolean { + var any = false + elements.forEach { any = add(it) || any } + return any + } + + override fun clear() { + if (backingSet.isNotEmpty()) { + backingSet.clear() + onClear() + } + } + + override fun removeAll(elements: Collection): Boolean { + var any = false + elements.forEach { any = remove(it) || any } + return any + } + + override fun retainAll(elements: Collection): Boolean { + var any = false + + val iterator = iterator() + + for (value in iterator) { + if (value !in elements) { + any = true + iterator.remove() + } + } + + return any + } + + override fun contains(element: E): Boolean { + return element in backingSet + } + + override fun containsAll(elements: Collection): Boolean { + return elements.all { it in backingSet } + } + + override fun isEmpty(): Boolean { + return backingSet.isEmpty() + } + + override val size: Int + get() = backingSet.size + + override fun iterator(): MutableIterator { + return object : MutableIterator { + private val parent = backingSet.iterator() + private var lastElement: Any? = Mark + + override fun hasNext(): Boolean { + return parent.hasNext() + } + + override fun next(): E { + return parent.next().also { lastElement = it } + } + + override fun remove() { + parent.remove() + val lastElement = lastElement + + if (lastElement !== Mark) { + this.lastElement = Mark + onValueRemoved(lastElement as E) + } + } + } + } + + private object Mark +} diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/ChangesetAction.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/ChangesetAction.kt deleted file mode 100644 index 20c3952..0000000 --- a/src/main/kotlin/ru/dbotthepony/kommons/io/ChangesetAction.kt +++ /dev/null @@ -1,5 +0,0 @@ -package ru.dbotthepony.kommons.io - -enum class ChangesetAction { - CLEAR, ADD, REMOVE -} diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/DecimalIO.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/DecimalIO.kt deleted file mode 100644 index 452cf82..0000000 --- a/src/main/kotlin/ru/dbotthepony/kommons/io/DecimalIO.kt +++ /dev/null @@ -1,23 +0,0 @@ -package ru.dbotthepony.kommons.io - -import ru.dbotthepony.kommons.math.Decimal -import java.io.DataInputStream -import java.io.DataOutputStream -import java.io.InputStream -import java.io.OutputStream - -fun InputStream.readDecimal(): Decimal { - val size = readVarInt() - require(size >= 0) { "Negative payload size: $size" } - val bytes = ByteArray(size) - read(bytes) - return Decimal.fromByteArray(bytes) -} - -fun OutputStream.writeDecimal(value: Decimal) { - val bytes = value.toByteArray() - writeVarInt(bytes.size) - write(bytes) -} - -val DecimalValueCodec = StreamCodec.Impl(DataInputStream::readDecimal, DataOutputStream::writeDecimal) diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/DelegateSyncher.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/DelegateSyncher.kt new file mode 100644 index 0000000..a5785ef --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kommons/io/DelegateSyncher.kt @@ -0,0 +1,768 @@ +package ru.dbotthepony.kommons.io + +import it.unimi.dsi.fastutil.ints.IntAVLTreeSet +import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import ru.dbotthepony.kommons.collect.ListenableMap +import ru.dbotthepony.kommons.collect.ListenableSet +import ru.dbotthepony.kommons.math.Decimal +import ru.dbotthepony.kommons.util.Delegate +import ru.dbotthepony.kommons.util.DelegateGetter +import ru.dbotthepony.kommons.util.DelegateSetter +import ru.dbotthepony.kommons.util.Listenable +import ru.dbotthepony.kommons.util.ListenableDelegate +import ru.dbotthepony.kommons.util.Observer +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.InputStream +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.locks.ReentrantLock +import java.util.function.Consumer +import java.util.function.Supplier +import kotlin.ConcurrentModificationException +import kotlin.collections.ArrayList +import kotlin.concurrent.withLock + +/** + * Synches attached delegates by serializing delta state on one end and deserializing on another. + * + * Precautions when using this class: + * 1. Entire class is **not** thread safe, with the exception of attached delegates triggering [Listenable.addListener] listeners + * (this includes calling [observe] from multiple threads, but why would you do this). + * 2. [addListener] listeners are expected to be called once when [isDirty] turns true **on best effort**. + * If syncher is running in highly congested environment (multiple threads are mutating attached delegates) + * listeners may be called more than once IF [constructPayload] call is ongoing. + * 3. [addListener] listeners on both [DelegateSyncher] and [DelegateSyncher.Slot]s are called on the same thread + * where delegate mutation/observation/etc occurred. If you need to mutate [DelegateSyncher] internal structure (add/remove + * slots) you **MUST** synchronize mutations by yourself if listener in question is expected to be called from multiple threads. + * 4. When using [DelegateSyncher] in single-threaded context, all operations are legal at any point in time, including [DelegateSyncher.Slot] + * addition/removal during [observe], [readPayload] or [addListener] callback on either [DelegateSyncher] or [DelegateSyncher.Slot]. + * 5. Calling [DelegateSyncher.AbstractSlot.remove] does not shift any subsequent slots down, leaving all slot IDs intact. + * Gaps are filled when new slots are created. + */ +class DelegateSyncher : Listenable, Observer { + private val slots = ArrayList() + private val observers = CopyOnWriteArrayList() + private val dirtySlots = ConcurrentLinkedQueue() + private val dirtySubs = Listenable.Void() + private val gaps = IntAVLTreeSet() + + @Volatile + private var changeset = 0 + + var isRemote = false + private set + + private val isDirtyF = AtomicBoolean() + val isDirty: Boolean + get() = isDirtyF.get() + + override fun observe(): Boolean { + var any = false + observers.forEach { any = it.observe() || any } + return any + } + + override fun addListener(listener: Runnable): Listenable.L { + return dirtySubs.addListener(listener) + } + + override fun addListener(listener: Consumer): Listenable.L { + return dirtySubs.addListener(listener) + } + + fun constructPayload(): FastByteArrayOutputStream? { + if (!isDirtyF.get()) return null + + val slots = ObjectAVLTreeSet() + + do { + var poll = dirtySlots.poll() + + while (poll != null) { + slots.add(poll) + poll = dirtySlots.poll() + } + } while (isDirtyF.compareAndSet(true, false)) + + val stream = FastByteArrayOutputStream() + val dataStream = DataOutputStream(stream) + + for (slot in slots) { + dataStream.writeVarInt(slot.id + 1) + slot.write(dataStream) + } + + dataStream.writeVarInt(0) + return stream + } + + fun readPayload(stream: DataInputStream) { + var nextId = stream.readVarInt() + + while (nextId > 0) { + val slot = slots + nextId = stream.readVarInt() + } + } + + fun readPayload(stream: InputStream) { + readPayload(DataInputStream(stream)) + } + + abstract inner class AbstractSlot : Observer, Comparable { + protected val isDirtySlotF = AtomicBoolean(true) + + val isDirty: Boolean + get() = isDirtySlotF.get() + + var isRemoved = false + private set + + val id: Int + + init { + val changeset = ++this@DelegateSyncher.changeset + + if (gaps.isNotEmpty()) { + val gap = gaps.firstInt() + gaps.remove(gap) + id = gap + check(slots[id] == null) { "Expected slot $id to be empty" } + slots[id] = this + } else { + id = slots.size + slots.add(this) + } + + dirtySlots.add(this) + + if (changeset != ++this@DelegateSyncher.changeset) { + throw ConcurrentModificationException("Manipulating DelegateSyncher across multiple threads") + } + + if (isDirtyF.compareAndSet(false, true)) { + dirtySubs.run() + } + } + + final override fun compareTo(other: AbstractSlot): Int { + return id.compareTo(other.id) + } + + protected abstract fun actuallyRemove() + + protected fun markDirty() { + check(!isRemoved) { "This network slot was removed" } + + if (!isRemote && isDirtySlotF.compareAndSet(false, true)) { + dirtySlots.add(this) + + if (!isDirtyF.get() && !dirtySlots.isEmpty() && isDirtyF.compareAndSet(false, true) && !dirtySlots.isEmpty()) { + dirtySubs.run() + } + } + } + + fun remove() { + if (!isRemoved) { + val changeset = ++this@DelegateSyncher.changeset + + isRemoved = true + slots[id] = null + observers.remove(this) + + if (id == slots.size - 1) { + slots.removeAt(id) + } else { + gaps.add(id) + } + + if (isDirtySlotF.get()) { + dirtySlots.remove(this) + this@DelegateSyncher.isDirtyF.set(dirtySlots.isNotEmpty()) + } + + actuallyRemove() + + if (changeset != ++this@DelegateSyncher.changeset) { + throw ConcurrentModificationException("Manipulating DelegateSyncher across multiple threads") + } + } + } + + abstract fun write(stream: DataOutputStream) + abstract fun read(stream: DataInputStream) + } + + inner class Slot(private val parent: ListenableDelegate, private val codec: StreamCodec) : AbstractSlot(), ListenableDelegate { + private val subs = Listenable.Impl() + + private val parentSub = parent.addListener(Consumer { + markDirty() + subs.accept(it) + }) + + override fun get(): V { + check(!isRemoved) { "This network slot was removed" } + return parent.get() + } + + override fun accept(t: V) { + check(!isRemoved) { "This network slot was removed" } + parent.accept(t) + } + + override fun addListener(listener: Consumer): Listenable.L { + check(!isRemoved) { "This network slot was removed" } + return subs.addListener(listener) + } + + override fun write(stream: DataOutputStream) { + check(!isRemoved) { "This network slot was removed" } + codec.write(stream, parent.get()) + isDirtySlotF.set(false) + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "This network slot was removed" } + parent.accept(codec.read(stream)) + } + + override fun observe(): Boolean { + check(!isRemoved) { "This network slot was removed" } + return false + } + + override fun actuallyRemove() { + subs.clear() + parentSub.remove() + } + } + + inner class ObservedSlot(private val parent: Delegate, private val codec: StreamCodec) : AbstractSlot(), ListenableDelegate { + private var lastValue: Any? = Mark + private val localLock = ReentrantLock() // avoid deadlock when code inside subscribers cause another value observation + private val subs = Listenable.Impl() + + override fun actuallyRemove() { + subs.clear() + lastValue = Mark + } + + override fun write(stream: DataOutputStream) { + check(!isRemoved) { "This network slot was removed" } + codec.write(stream, parent.get()) + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "This network slot was removed" } + val read = codec.read(stream) + parent.accept(read) + + localLock.withLock { + lastValue = read + subs.accept(read) + } + } + + override fun observe(): Boolean { + check(!isRemoved) { "This network slot was removed" } + if (isRemote) return false + + val get = get() + + if (lastValue === Mark || !codec.compare(get, lastValue as V)) { + localLock.withLock { + if (lastValue === Mark || !codec.compare(get, lastValue as V)) { + markDirty() + lastValue = codec.copy(get) + subs.accept(get) + return true + } + } + } + + return false + } + + override fun get(): V { + check(!isRemoved) { "This network slot was removed" } + val get = get() + + if (!isRemote) { + if (lastValue === Mark || !codec.compare(get, lastValue as V)) { + localLock.withLock { + if (lastValue === Mark || !codec.compare(get, lastValue as V)) { + markDirty() + lastValue = codec.copy(get) + subs.accept(get) + } + } + } + } + + return get + } + + override fun accept(t: V) { + check(!isRemoved) { "This network slot was removed" } + + parent.accept(t) + + localLock.withLock { + lastValue = codec.copy(t) + subs.accept(t) + } + } + + override fun addListener(listener: Consumer): Listenable.L { + check(!isRemoved) { "This network slot was removed" } + return subs.addListener(listener) + } + } + + inner class ComputedSlot(parent: Supplier, private val codec: StreamCodec) : AbstractSlot(), ListenableDelegate { + private val shadow = ListenableDelegate.CustomShadow(parent, codec::compare, codec::copy) + + override fun write(stream: DataOutputStream) { + check(!isRemoved) { "This network slot was removed" } + codec.write(stream, shadow.get()) + isDirtySlotF.set(false) + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "This network slot was removed" } + shadow.accept(codec.read(stream)) + } + + override fun observe(): Boolean { + check(!isRemoved) { "This network slot was removed" } + if (shadow.observe()) { + markDirty() + return true + } + + return false + } + + override fun get(): V { + check(!isRemoved) { "This network slot was removed" } + return shadow.get() + } + + override fun accept(t: V) { + check(!isRemoved) { "This network slot was removed" } + shadow.accept(t) + } + + override fun addListener(listener: Consumer): Listenable.L { + check(!isRemoved) { "This network slot was removed" } + return shadow.addListener(listener) + } + + override fun actuallyRemove() { + shadow.clearListeners() + } + } + + inner class SetSlot(val value: ListenableSet, private val codec: StreamCodec) : AbstractSlot(), ListenableSet.SetListener, Listenable> { + private val sub = value.addListener(this) + private val subs = Listenable.Impl>() + private val backlog = ArrayList Unit>() + + override fun onClear() { + check(!isRemoved) { "This network slot was removed" } + synchronized(backlog) { + markDirty() + + backlog.clear() + backlog.add { write(CLEAR) } + } + } + + override fun onValueAdded(element: E) { + check(!isRemoved) { "This network slot was removed" } + val copy = codec.copy(element) + + synchronized(backlog) { + markDirty() + + backlog.add { + write(ADD) + codec.write(this, copy) + } + } + } + + override fun onValueRemoved(element: E) { + check(!isRemoved) { "This network slot was removed" } + val copy = codec.copy(element) + + synchronized(backlog) { + markDirty() + + backlog.add { + write(REMOVE) + codec.write(this, copy) + } + } + } + + override fun actuallyRemove() { + backlog.clear() + sub.remove() + subs.clear() + } + + override fun write(stream: DataOutputStream) { + check(!isRemoved) { "This network slot was removed" } + synchronized(backlog) { + isDirtySlotF.set(false) + backlog.forEach { it(stream) } + backlog.clear() + } + + stream.write(END) + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "This network slot was removed" } + var action = stream.read() + + while (true) { + when (action) { + ADD -> value.add(codec.read(stream)) + REMOVE -> value.remove(codec.read(stream)) + CLEAR -> value.clear() + else -> break + } + + action = stream.read() + } + + subs.accept(value) + } + + override fun observe(): Boolean { + check(!isRemoved) { "This network slot was removed" } + return false + } + + override fun addListener(listener: Consumer>): Listenable.L { + check(!isRemoved) { "This network slot was removed" } + return subs.addListener(listener) + } + } + + inner class MapSlot(val value: ListenableMap, private val keyCodec: StreamCodec, private val valueCodec: StreamCodec) : AbstractSlot(), Listenable>, ListenableMap.MapListener { + private val sub = value.addListener(this) + private val subs = Listenable.Impl>() + private val backlog = ArrayList Unit>() + + override fun onClear() { + check(!isRemoved) { "This network slot was removed" } + synchronized(backlog) { + markDirty() + + backlog.clear() + backlog.add { write(CLEAR) } + } + } + + override fun onValueAdded(key: K, value: V) { + check(!isRemoved) { "This network slot was removed" } + val keyCopy = keyCodec.copy(key) + val valueCopy = valueCodec.copy(value) + + synchronized(backlog) { + markDirty() + + backlog.add { + write(ADD) + keyCodec.write(this, keyCopy) + valueCodec.write(this, valueCopy) + } + } + } + + override fun onValueRemoved(key: K, value: V) { + check(!isRemoved) { "This network slot was removed" } + val keyCopy = keyCodec.copy(key) + + synchronized(backlog) { + markDirty() + + backlog.add { + write(REMOVE) + keyCodec.write(this, keyCopy) + } + } + } + + override fun actuallyRemove() { + backlog.clear() + sub.remove() + subs.clear() + } + + override fun write(stream: DataOutputStream) { + check(!isRemoved) { "This network slot was removed" } + synchronized(backlog) { + isDirtySlotF.set(false) + backlog.forEach { it(stream) } + backlog.clear() + } + + stream.write(END) + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "This network slot was removed" } + var action = stream.read() + + while (true) { + when (action) { + ADD -> value.put(keyCodec.read(stream), valueCodec.read(stream)) + REMOVE -> value.remove(keyCodec.read(stream)) + CLEAR -> value.clear() + else -> break + } + + action = stream.read() + } + + subs.accept(value) + } + + override fun addListener(listener: Consumer>): Listenable.L { + check(!isRemoved) { "This network slot was removed" } + return subs.addListener(listener) + } + + override fun observe(): Boolean { + check(!isRemoved) { "This network slot was removed" } + return false + } + } + + fun add(delegate: ListenableDelegate, codec: StreamCodec): Slot { + return Slot(delegate, codec) + } + + fun add(delegate: Delegate, codec: StreamCodec): ObservedSlot { + return ObservedSlot(delegate, codec) + } + + fun add(delegate: Supplier, codec: StreamCodec): ComputedSlot { + return ComputedSlot(delegate, codec) + } + + fun add(delegate: ListenableSet, codec: StreamCodec): SetSlot { + return SetSlot(delegate, codec) + } + + fun add(delegate: ListenableMap, keyCodec: StreamCodec, valueCodec: StreamCodec): MapSlot { + return MapSlot(delegate, keyCodec, valueCodec) + } + + @JvmName("vbyte") + @JvmOverloads + fun byte(value: Byte = 0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), ByteValueCodec) + } + + @JvmName("vshort") + @JvmOverloads + fun short(value: Short = 0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), ShortValueCodec) + } + + @JvmName("vchar") + @JvmOverloads + fun char(value: Char, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), CharValueCodec) + } + + @JvmName("vint") + @JvmOverloads + fun int(value: Int = 0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), VarIntValueCodec) + } + + @JvmName("vlong") + @JvmOverloads + fun long(value: Long = 0L, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), VarLongValueCodec) + } + + @JvmName("vfloat") + @JvmOverloads + fun float(value: Float = 0f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), FloatValueCodec) + } + + @JvmName("vdouble") + @JvmOverloads + fun double(value: Double = 0.0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), DoubleValueCodec) + } + + @JvmName("vboolean") + @JvmOverloads + fun boolean(value: Boolean = false, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), BooleanValueCodec) + } + + @JvmOverloads + fun string(value: String, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), BinaryStringCodec) + } + + @JvmOverloads + fun uuid(value: UUID, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), UUIDValueCodec) + } + + @JvmOverloads + fun decimal(value: Decimal = Decimal.ZERO, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): Slot { + return add(ListenableDelegate.maskSmart(value, getter, setter), DecimalValueCodec) + } + + private object Mark + + /** + * Helper class which allows quick creation of [DelegateSyncher] from attached delegates. + */ + class Group { + private val constructors = ArrayList Unit>() + + fun , V> add(delegate: D, codec: StreamCodec): D { + constructors.add { + add(delegate, codec) + } + + return delegate + } + + fun , V> add(delegate: D, codec: StreamCodec): D { + constructors.add { + add(delegate, codec) + } + + return delegate + } + + fun , E> add(delegate: D, codec: StreamCodec): D { + constructors.add { + add(delegate, codec) + } + + return delegate + } + + fun , K, V> add(delegate: D, keyCodec: StreamCodec, valueCodec: StreamCodec): D { + constructors.add { + add(delegate, keyCodec, valueCodec) + } + + return delegate + } + + @JvmName("vbyte") + @JvmOverloads + fun byte(value: Byte = 0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), ByteValueCodec) + } + + @JvmName("vshort") + @JvmOverloads + fun short(value: Short = 0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), ShortValueCodec) + } + + @JvmName("vchar") + @JvmOverloads + fun char(value: Char, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), CharValueCodec) + } + + @JvmName("vint") + @JvmOverloads + fun int(value: Int = 0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), VarIntValueCodec) + } + + @JvmName("vlong") + @JvmOverloads + fun long(value: Long = 0L, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), VarLongValueCodec) + } + + @JvmName("vfloat") + @JvmOverloads + fun float(value: Float = 0f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), FloatValueCodec) + } + + @JvmName("vdouble") + @JvmOverloads + fun double(value: Double = 0.0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), DoubleValueCodec) + } + + @JvmName("vboolean") + @JvmOverloads + fun boolean(value: Boolean = false, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), BooleanValueCodec) + } + + @JvmOverloads + fun string(value: String, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), BinaryStringCodec) + } + + @JvmOverloads + fun uuid(value: UUID, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), UUIDValueCodec) + } + + @JvmOverloads + fun decimal(value: Decimal = Decimal.ZERO, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate { + return add(ListenableDelegate.maskSmart(value, getter, setter), DecimalValueCodec) + } + + fun set(codec: StreamCodec, backing: MutableSet = ObjectOpenHashSet()): ListenableSet { + val delegate = ListenableSet(backing) + constructors.add { add(delegate, codec) } + return delegate + } + + fun map(keyCodec: StreamCodec, valueCodec: StreamCodec, backing: MutableMap = Object2ObjectOpenHashMap()): ListenableMap { + val delegate = ListenableMap(backing) + constructors.add { add(delegate, keyCodec, valueCodec) } + return delegate + } + + fun create(): DelegateSyncher { + return addInto(DelegateSyncher()) + } + + fun addInto(syncher: DelegateSyncher): DelegateSyncher { + constructors.forEach { it(syncher) } + return syncher + } + } + + companion object { + const val END = 0 + const val CLEAR = 1 + const val ADD = 2 + const val REMOVE = 3 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/InputStreamUtils.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/InputStreamUtils.kt index 9d26366..666545f 100644 --- a/src/main/kotlin/ru/dbotthepony/kommons/io/InputStreamUtils.kt +++ b/src/main/kotlin/ru/dbotthepony/kommons/io/InputStreamUtils.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.kommons.io import it.unimi.dsi.fastutil.bytes.ByteArrayList +import ru.dbotthepony.kommons.math.Decimal import java.io.DataInput import java.io.EOFException import java.io.IOException @@ -210,3 +211,11 @@ fun InputStream.readUTF(): String { fun InputStream.readUUID(): UUID { return UUID(readLong(), readLong()) } + +fun InputStream.readDecimal(): Decimal { + val size = readVarInt() + require(size >= 0) { "Negative payload size: $size" } + val bytes = ByteArray(size) + read(bytes) + return Decimal.fromByteArray(bytes) +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/MapChangeset.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/MapChangeset.kt deleted file mode 100644 index 5d29eab..0000000 --- a/src/main/kotlin/ru/dbotthepony/kommons/io/MapChangeset.kt +++ /dev/null @@ -1,23 +0,0 @@ -package ru.dbotthepony.kommons.io - -data class MapChangeset( - val action: ChangesetAction, - val key: K?, - val value: V? -) { - inline fun map(add: (K, V) -> Unit, remove: (K) -> Unit) { - when (action) { - ChangesetAction.ADD -> add.invoke(key!!, value!!) - ChangesetAction.REMOVE -> remove.invoke(key!!) - else -> {} - } - } - - inline fun map(add: (K, V) -> Unit, remove: (K) -> Unit, clear: () -> Unit) { - when (action) { - ChangesetAction.CLEAR -> clear.invoke() - ChangesetAction.ADD -> add.invoke(key!!, value!!) - ChangesetAction.REMOVE -> remove.invoke(key!!) - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/OutputStreamUtils.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/OutputStreamUtils.kt index 9c9c826..75df993 100644 --- a/src/main/kotlin/ru/dbotthepony/kommons/io/OutputStreamUtils.kt +++ b/src/main/kotlin/ru/dbotthepony/kommons/io/OutputStreamUtils.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kommons.io +import ru.dbotthepony.kommons.math.Decimal import ru.dbotthepony.kommons.util.IStruct2b import ru.dbotthepony.kommons.util.IStruct2d import ru.dbotthepony.kommons.util.IStruct2f @@ -250,3 +251,9 @@ fun OutputStream.writeStruct4b(value: IStruct4b) { writeShort(value.component3().toInt() and 0xFF) writeShort(value.component4().toInt() and 0xFF) } + +fun OutputStream.writeDecimal(value: Decimal) { + val bytes = value.toByteArray() + writeVarInt(bytes.size) + write(bytes) +} diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/SetChangeset.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/SetChangeset.kt deleted file mode 100644 index ded392e..0000000 --- a/src/main/kotlin/ru/dbotthepony/kommons/io/SetChangeset.kt +++ /dev/null @@ -1,22 +0,0 @@ -package ru.dbotthepony.kommons.io - -data class SetChangeset( - val action: ChangesetAction, - val value: V? -) { - inline fun map(add: (V) -> Unit, remove: (V) -> Unit) { - when (action) { - ChangesetAction.ADD -> add.invoke(value!!) - ChangesetAction.REMOVE -> remove.invoke(value!!) - else -> {} - } - } - - inline fun map(add: (V) -> Unit, remove: (V) -> Unit, clear: () -> Unit) { - when (action) { - ChangesetAction.CLEAR -> clear.invoke() - ChangesetAction.ADD -> add.invoke(value!!) - ChangesetAction.REMOVE -> remove.invoke(value!!) - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/StreamCodec.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/StreamCodec.kt index 3f9d042..bddf2e9 100644 --- a/src/main/kotlin/ru/dbotthepony/kommons/io/StreamCodec.kt +++ b/src/main/kotlin/ru/dbotthepony/kommons/io/StreamCodec.kt @@ -7,22 +7,18 @@ import kotlin.reflect.KClass /** * Represents value which can be encoded onto or decoded from stream. - * - * Also provides [copy] and [compare] methods */ interface StreamCodec { fun read(stream: DataInputStream): V fun write(stream: DataOutputStream, value: V) /** - * defensive copy + * Defensive copy */ fun copy(value: V): V /** - * Optional equality check override. Utilized to determine whenever e.g. network value is different from new value - * - * By default uses [Any.equals] + * Optional custom equality check */ fun compare(a: V, b: V): Boolean { return a == b @@ -170,6 +166,7 @@ val UUIDValueCodec = StreamCodec.Impl({ s -> UUID(s.readLong(), s.readLong()) }, val VarIntValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarInt, DataOutputStream::writeSignedVarInt) val VarLongValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarLong, DataOutputStream::writeSignedVarLong) val BinaryStringCodec = StreamCodec.Impl(DataInputStream::readBinaryString, DataOutputStream::writeBinaryString) +val DecimalValueCodec = StreamCodec.Impl(DataInputStream::readDecimal, DataOutputStream::writeDecimal) fun > Class.codec() = StreamCodec.Enum(this) fun > KClass.codec() = StreamCodec.Enum(this.java) diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/SynchedDelegates.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/SynchedDelegates.kt deleted file mode 100644 index aaeb942..0000000 --- a/src/main/kotlin/ru/dbotthepony/kommons/io/SynchedDelegates.kt +++ /dev/null @@ -1,922 +0,0 @@ -@file:Suppress("DeprecatedCallableAddReplaceWith") - -package ru.dbotthepony.kommons.io - -import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream -import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction -import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap -import it.unimi.dsi.fastutil.objects.ReferenceArraySet -import ru.dbotthepony.kommons.collect.ProxiedMap -import ru.dbotthepony.kommons.collect.forValidRefs -import ru.dbotthepony.kommons.util.Observer -import ru.dbotthepony.kommons.util.ListenableDelegate -import ru.dbotthepony.kommons.util.DelegateGetter -import ru.dbotthepony.kommons.util.DelegateSetter -import ru.dbotthepony.kommons.util.Listenable -import java.io.DataInputStream -import java.io.DataOutputStream -import java.io.InputStream -import java.lang.ref.WeakReference -import java.util.* -import java.util.function.Consumer -import java.util.function.Supplier - -/** - * Universal, one-to-many value synchronizer, allowing to synchronize values from server to client - * anywhere, where input/output streams are supported - */ -@Suppress("unused") -class SynchedDelegates(private val callback: Runnable, private val alwaysCallCallback: Boolean) { - constructor() : this(Runnable {}, false) - constructor(callback: Runnable) : this(callback, false) - - private var freeSlots = 0 - // почему не удалять поля напрямую? - // чтоб не возникло проблем в состоянии гонки - // формируем пакет -> удаляем поле по обе стороны -> клиент принимает пакет -> клиент считывает неверные данные - // конечно, всё равно всё сломается если было удалено поле, которое находится в пакете - // но если поля нет в пакете, то всё окей - private val fields = ArrayList(0) - private val observers = ArrayList(0) - private val endpoints = ArrayList>(1) - private var lastEndpointCleanup = System.nanoTime() - private var nextFieldID = 0 - val hasObservers: Boolean get() = observers.isNotEmpty() - var isEmpty: Boolean = true - private set - val isNotEmpty: Boolean get() = !isEmpty - var isDirty: Boolean = false - private set(value) { - if (value != field) { - field = value - - if (value && !alwaysCallCallback) { - callback.run() - } - } - - if (alwaysCallCallback && value) { - callback.run() - } - } - - private val boundEndpoints = Collections.synchronizedMap(WeakHashMap()) - - fun computeEndpointFor(obj: Any): Endpoint { - return boundEndpoints.computeIfAbsent(obj) { Endpoint() } - } - - fun removeEndpointFor(obj: Any): Endpoint? { - return boundEndpoints.remove(obj) - } - - fun endpointFor(obj: Any): Endpoint? { - return boundEndpoints[obj] - } - - fun markClean() { - isDirty = false - } - - private fun notifyEndpoints(dirtyField: AbstractValue) { - isDirty = true - - forEachEndpoint { - it.addDirty(dirtyField) - } - } - - private inline fun forEachEndpoint(execute: (Endpoint) -> Unit) { - synchronized(endpoints) { - endpoints.forValidRefs { execute.invoke(it) } - } - } - - val defaultEndpoint = Endpoint() - - inner class Endpoint { - init { - synchronized(endpoints) { - endpoints.add(WeakReference(this)) - - if (System.nanoTime() - lastEndpointCleanup >= 60) { - lastEndpointCleanup = System.nanoTime() - - val iterator = endpoints.listIterator() - - for (value in iterator) { - if (value.get() == null) { - iterator.remove() - } - } - } - } - } - - private val dirty = ReferenceArraySet(4) - - // use LinkedList because it is ensured memory is freed on LinkedList#clear - private val mapBacklogs = Reference2ObjectOpenHashMap, LinkedList Unit>>>() - private val setBacklogs = Reference2ObjectOpenHashMap, LinkedList Unit>>>() - - var isDisabled: Boolean = false - private set - - fun disable() { - if (isDisabled) return - isDisabled = true - mapBacklogs.clear() - dirty.clear() - - synchronized(endpoints) { - val iterator = endpoints.listIterator() - - for (value in iterator) { - if (value.get() === this || value.get() == null) { - iterator.remove() - } - } - } - } - - init { - markDirty() - } - - fun markDirty() { - if (isDisabled) return - - for (field in fields) { - field?.markDirty(this) - } - } - - internal fun addDirty(field: AbstractValue) { - if (isDisabled) return - dirty.add(field) - } - - internal fun removeDirty(field: AbstractValue) { - dirty.remove(field) - } - - internal fun getMapBacklog(map: Map): LinkedList Unit>> { - if (isDisabled) return LinkedList() - - return mapBacklogs.computeIfAbsent(map, Reference2ObjectFunction { - LinkedList() - }) - } - - internal fun removeMapBacklog(map: Map) { - mapBacklogs.remove(map) - } - - internal fun getSetBacklog(set: Set): LinkedList Unit>> { - if (isDisabled) return LinkedList() - - return setBacklogs.computeIfAbsent(set, Reference2ObjectFunction { - LinkedList() - }) - } - - internal fun removeSetBacklog(set: Set) { - setBacklogs.remove(set) - } - - fun collectData(): FastByteArrayOutputStream? { - if (isDisabled || dirty.isEmpty()) - return null - - val stream = FastByteArrayOutputStream() - val dataStream = DataOutputStream(stream) - - for (field in dirty) { - stream.writeVarInt(field.id) - field.write(dataStream, this) - } - - dirty.clear() - stream.write(0) - - return stream - } - } - - abstract inner class AbstractValue : Observer { - val id: Int - - init { - if (freeSlots > 0) { - var found = -1 - - for (i in fields.indices) { - if (fields[i] == null) { - fields[i] = this - found = i + 1 - freeSlots-- - break - } - } - - if (found == -1) { - throw RuntimeException("freeSlots = $freeSlots but no null entries in field list!") - } else { - id = found - } - } else { - fields.add(this) - id = fields.size - isEmpty = false - } - } - - var isRemoved = false - private set - - protected var isDirty = false - protected var isRemote = false - - open fun remove() { - if (isRemoved) - return - - isRemoved = true - freeSlots++ - fields[id - 1] = null - observers.remove(this) - isEmpty = fields.all { it == null } - - while (fields[fields.size - 1] == null) { - fields.removeAt(fields.size - 1) - freeSlots-- - } - - forEachEndpoint { - it.removeDirty(this) - } - } - - open fun markDirty(endpoint: Endpoint) { - check(!isRemoved) { "Field was removed" } - endpoint.addDirty(this) - } - - open fun markDirty() { - check(!isRemoved) { "Field was removed" } - notifyEndpoints(this) - isDirty = true - } - - abstract fun write(stream: DataOutputStream, endpoint: Endpoint) - abstract fun read(stream: DataInputStream) - } - - inner class RegularValue( - value: T, - private val codec: StreamCodec, - getter: DelegateGetter = DelegateGetter.passthrough(), - setter: DelegateSetter = DelegateSetter.passthrough() - ) : AbstractValue(), ListenableDelegate by ListenableDelegate.maskSmart(value, getter, setter) { - init { - addListener(Runnable { - if (!isDirty && !isRemote) { - notifyEndpoints(this) - isDirty = true - } - }) - } - - override fun observe(): Boolean { - return false - } - - override fun write(stream: DataOutputStream, endpoint: Endpoint) { - codec.write(stream, get()) - isDirty = false - } - - override fun read(stream: DataInputStream) { - isRemote = true - accept(codec.read(stream)) - isDirty = false - } - } - - inner class ObservedValue( - value: T, - private val codec: StreamCodec, - getter: DelegateGetter = DelegateGetter.passthrough(), - setter: DelegateSetter = DelegateSetter.passthrough() - ) : AbstractValue(), ListenableDelegate by ListenableDelegate.maskSmart(value, getter, setter) { - private var observed = codec.copy(value) - - init { - observers.add(this) - - addListener(Runnable { - if (!isDirty && !isRemote) { - notifyEndpoints(this) - isDirty = true - } - }) - } - - override fun observe(): Boolean { - if (!isRemote) { - val get = get() - - if (!codec.compare(get, observed)) { - observed = codec.copy(get) - isDirty = true - } - } - - return isDirty - } - - override fun write(stream: DataOutputStream, endpoint: Endpoint) { - codec.write(stream, get()) - isDirty = false - } - - override fun read(stream: DataInputStream) { - isRemote = true - accept(codec.read(stream)) - isDirty = false - } - } - - inner class ComputedValue(getter: Supplier, private val codec: StreamCodec) : AbstractValue(), ListenableDelegate { - private val shadow = ListenableDelegate.SmartShadow(getter, codec::compare, codec::copy) - - override fun get(): T { - return shadow.get() - } - - override fun accept(t: T) { - shadow.accept(t) - } - - override fun addListener(listener: Consumer): Listenable.L { - return shadow.addListener(listener) - } - - override fun observe(): Boolean { - return shadow.observe() - } - - init { - shadow.addListener(Runnable { - if (!isDirty && !isRemote) { - notifyEndpoints(this) - isDirty = true - } - }) - } - - override fun write(stream: DataOutputStream, endpoint: Endpoint) { - codec.write(stream, shadow.get()) - isDirty = false - } - - override fun read(stream: DataInputStream) { - isRemote = true - shadow.accept(codec.read(stream)) - isDirty = false - } - } - - @JvmName("vbyte") fun byte(value: Byte = 0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, ByteValueCodec, getter, setter) - @JvmName("vshort") fun short(value: Short = 0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, ShortValueCodec, getter, setter) - @JvmName("vchar") fun char(value: Char = 0.toChar(), getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, CharValueCodec, getter, setter) - @JvmName("vint") fun int(value: Int = 0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, IntValueCodec, getter, setter) - @JvmName("vlong") fun long(value: Long = 0L, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, LongValueCodec, getter, setter) - @JvmName("vfloat") fun float(value: Float = 0f, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, FloatValueCodec, getter, setter) - @JvmName("vdouble") fun double(value: Double = 0.0, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, DoubleValueCodec, getter, setter) - @JvmName("vboolean") fun boolean(value: Boolean = false, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, BooleanValueCodec, getter, setter) - - fun string(value: String, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, BinaryStringCodec, getter, setter) - fun uuid(value: UUID, getter: DelegateGetter = DelegateGetter.passthrough(), setter: DelegateSetter = DelegateSetter.passthrough()): ListenableDelegate = RegularValue(value, UUIDValueCodec, getter, setter) - - inner class Set( - private val codec: StreamCodec, - private val backingSet: MutableSet, - private val callback: ((changes: Collection>) -> Unit)? = null, - ) : AbstractValue() { - private fun pushBacklog(element: E, action: (DataOutputStream) -> Unit) { - check(!isRemote) { "Field marked as remote" } - - val pair = element to action - - forEachEndpoint { - val list = it.getSetBacklog(this) - val iterator = list.listIterator() - - for (value in iterator) { - if (value.first == element) { - iterator.remove() - } - } - - list.addLast(pair) - } - } - - override fun observe(): Boolean { - return false - } - - override fun remove() { - if (!isRemoved) { - forEachEndpoint { it.removeSetBacklog(this) } - } - - super.remove() - } - - override fun markDirty() { - check(!isRemoved) { "Field was removed" } - - if (isRemote || endpoints.isEmpty()) - return - - isDirty = true - - val endpoints = LinkedList Unit>>>() - - forEachEndpoint { - endpoints.add(it.getSetBacklog(this)) - it.addDirty(this) - } - - endpoints.forEach { - it.clear() - it.add(null to ClearBacklogEntry) - } - - for (value in backingSet) { - val valueCopy = codec.copy(value) - val pair: Pair Unit> = valueCopy to { it.write(ChangesetAction.ADD.ordinal + 1); codec.write(it, valueCopy) } - - endpoints.forEach { - it.add(pair) - } - } - } - - override fun markDirty(endpoint: Endpoint) { - super.markDirty(endpoint) - - endpoint.getSetBacklog(this).let { - it.clear() - it.add(null to ClearBacklogEntry) - - for (value in backingSet) { - val valueCopy = codec.copy(value) - it.add(valueCopy to { it.write(ChangesetAction.ADD.ordinal + 1); codec.write(it, valueCopy) }) - } - } - } - - val value: MutableSet = object : MutableSet { - override fun add(element: E): Boolean { - if (backingSet.add(element)) { - if (!isRemote) { - markDirty() - - val copy = codec.copy(element) - - pushBacklog(element) { - it.write(ChangesetAction.ADD.ordinal + 1) - codec.write(it, copy) - } - } - - return true - } - - return false - } - - override fun addAll(elements: Collection): Boolean { - var any = false - elements.forEach { any = add(it) || any } - return any - } - - override fun clear() { - if (backingSet.isNotEmpty()) { - backingSet.clear() - - if (!isRemote) { - markDirty() - - forEachEndpoint { - it.getSetBacklog(this@Set).let { - it.clear() - it.add(null to ClearBacklogEntry) - } - } - } - } - } - - override fun iterator(): MutableIterator { - return object : MutableIterator { - private val parent = backingSet.iterator() - private var lastElement: Any? = Mark - - override fun hasNext(): Boolean { - return parent.hasNext() - } - - override fun next(): E { - return parent.next().also { lastElement = it } - } - - override fun remove() { - parent.remove() - val lastElement = lastElement - - if (lastElement !== Mark) { - this.lastElement = Mark - - if (!isRemote) { - markDirty() - - @Suppress("unchecked_cast") - pushBacklog(lastElement as E) { - it.write(ChangesetAction.REMOVE.ordinal + 1) - codec.write(it, lastElement as E) - } - } - } - } - } - } - - override fun remove(element: E): Boolean { - if (backingSet.remove(element)) { - if (!isRemote) { - markDirty() - - val copy = codec.copy(element) - - pushBacklog(element) { - it.write(ChangesetAction.REMOVE.ordinal + 1) - codec.write(it, copy) - } - } - - return true - } - - return false - } - - override fun removeAll(elements: Collection): Boolean { - var any = false - elements.forEach { any = remove(it) || any } - return any - } - - override fun retainAll(elements: Collection): Boolean { - var any = false - - val iterator = iterator() - - for (value in iterator) { - if (value !in elements) { - any = true - iterator.remove() - } - } - - return any - } - - override val size: Int - get() = backingSet.size - - override fun contains(element: E): Boolean { - return element in backingSet - } - - override fun containsAll(elements: Collection): Boolean { - return backingSet.containsAll(elements) - } - - override fun isEmpty(): Boolean { - return backingSet.isEmpty() - } - } - - override fun write(stream: DataOutputStream, endpoint: Endpoint) { - val list = endpoint.getSetBacklog(this) - - for (value in list) { - value.second.invoke(stream) - } - - stream.write(0) - list.clear() - isDirty = false - } - - override fun read(stream: DataInputStream) { - if (!isRemote) { - isRemote = true - forEachEndpoint { it.removeSetBacklog(this) } - } - - isDirty = false - - var action = stream.read() - val changeset = LinkedList>() - - while (action != 0) { - if ((action - 1) !in ChangesetActionList.indices) { - throw IllegalArgumentException("Unknown changeset action with index ${action - 1}") - } - - when (ChangesetActionList[action - 1]) { - ChangesetAction.CLEAR -> { - changeset.add(SetChangeset(ChangesetAction.CLEAR, null)) - backingSet.clear() - } - - ChangesetAction.ADD -> { - val read = codec.read(stream) - changeset.add(SetChangeset(ChangesetAction.ADD, read)) - backingSet.add(read) - } - - ChangesetAction.REMOVE -> { - val read = codec.read(stream) - changeset.add(SetChangeset(ChangesetAction.REMOVE, read)) - backingSet.remove(read) - } - } - - action = stream.read() - } - - callback?.invoke(changeset) - } - } - - inner class Map( - private val keyCodec: StreamCodec, - private val valueCodec: StreamCodec, - private val backingMap: MutableMap, - private val callback: ((changes: Collection>) -> Unit)? = null, - ) : AbstractValue() { - private var sentAllValues = false - - private fun pushBacklog(key: Any?, value: (DataOutputStream) -> Unit) { - val pair = key to value - - forEachEndpoint { - val list = it.getMapBacklog(this) - val iterator = list.listIterator() - - for (e in iterator) { - if (e.first == key) { - iterator.remove() - } - } - - list.addLast(pair) - } - } - - override fun observe(): Boolean { - return false - } - - override fun markDirty() { - check(!isRemoved) { "Field was removed" } - - if (isRemote || endpoints.isEmpty()) - return - - isDirty = true - val backlogs = LinkedList Unit>>>() - - forEachEndpoint { - it.addDirty(this) - val value = it.getMapBacklog(this) - backlogs.add(value) - value.clear() - value.add(null to ClearBacklogEntry) - } - - for ((key, value) in backingMap) { - val valueCopy = valueCodec.copy(value) - - val action = key to { it: DataOutputStream -> - it.write(ChangesetAction.ADD.ordinal + 1) - keyCodec.write(it, key) - valueCodec.write(it, valueCopy) - } - - for (backlog in backlogs) { - backlog.add(action) - } - } - } - - override fun markDirty(endpoint: Endpoint) { - check(!isRemoved) { "Field was removed" } - - if (isRemote) - return - - val backlog = endpoint.getMapBacklog(this) - - backlog.clear() - backlog.add(null to ClearBacklogEntry) - - for ((key, value) in backingMap) { - val valueCopy = valueCodec.copy(value) - - backlog.add(key to { - it.write(ChangesetAction.ADD.ordinal + 1) - keyCodec.write(it, key) - valueCodec.write(it, valueCopy) - }) - } - } - - private fun lmarkDirty() { - if (!isDirty) { - notifyEndpoints(this@Map) - isDirty = true - } - } - - val value: MutableMap = object : ProxiedMap(backingMap) { - override fun onClear() { - if (isRemote || endpoints.isEmpty()) - return - - forEachEndpoint { endpoint -> - endpoint.getMapBacklog(this@Map).let { - it.clear() - it.add(null to ClearBacklogEntry) - } - } - - lmarkDirty() - this@SynchedDelegates.isDirty = true - } - - override fun onValueAdded(key: K, value: V) { - if (isRemote || endpoints.isEmpty()) - return - - val keyCopy = keyCodec.copy(key) - val valueCopy = valueCodec.copy(value) - - pushBacklog(key) { - it.write(ChangesetAction.ADD.ordinal + 1) - keyCodec.write(it, keyCopy) - valueCodec.write(it, valueCopy) - } - - lmarkDirty() - } - - override fun onValueRemoved(key: K, value: V) { - if (isRemote || endpoints.isEmpty()) - return - - val keyCopy = keyCodec.copy(key) - - pushBacklog(key) { - it.write(ChangesetAction.REMOVE.ordinal + 1) - keyCodec.write(it, keyCopy) - } - - lmarkDirty() - } - } - - override fun write(stream: DataOutputStream, endpoint: Endpoint) { - sentAllValues = false - isDirty = false - - val iterator = endpoint.getMapBacklog(this).listIterator() - - for (entry in iterator) { - entry.second.invoke(stream) - iterator.remove() - } - - stream.write(0) - } - - override fun read(stream: DataInputStream) { - if (!isRemote) { - isRemote = true - forEachEndpoint { it.removeMapBacklog(this) } - } - - isDirty = false - - val changeset = LinkedList>() - var readAction = stream.read() - 1 - - while (readAction != -1) { - if (readAction >= ChangesetActionList.size) { - throw IndexOutOfBoundsException("Unknown map action with ID $readAction") - } - - when (ChangesetActionList[readAction]) { - ChangesetAction.CLEAR -> { - backingMap.clear() - changeset.add(ClearMapChangeset) - } - - ChangesetAction.ADD -> { - val key = keyCodec.read(stream) - val value = valueCodec.read(stream) - backingMap[key] = value - changeset.add(MapChangeset(ChangesetAction.ADD, key, value)) - } - - ChangesetAction.REMOVE -> { - val key = keyCodec.read(stream) - backingMap.remove(key) - changeset.add(MapChangeset(ChangesetAction.REMOVE, key, null)) - } - } - - readAction = stream.read() - 1 - } - - if (changeset.size != 0) { - callback?.invoke(changeset) - } - } - } - - /** - * marks all fields dirty, invalidates mappings for each endpoint - */ - fun invalidate() { - for (field in fields) { - field?.markDirty() - } - } - - /** - * Observe changes of all fields with backing computation lambda - */ - fun observe(): Boolean { - var changes = false - - if (observers.isNotEmpty()) { - for (field in observers) { - changes = field.observe() || changes - } - } - - return changes - } - - /** - * [defaultEndpoint]#collectNetworkPayload - */ - fun collectData(): FastByteArrayOutputStream? { - check(!defaultEndpoint.isDisabled) { "Default endpoint is not used" } - observe() - val values = defaultEndpoint.collectData() - markClean() - return values - } - - fun read(stream: InputStream): Int { - var fieldId = stream.readVarInt() - var i = 0 - val dataStream = DataInputStream(stream) - - while (fieldId != 0) { - val field = fields.getOrNull(fieldId - 1) ?: throw IllegalArgumentException("Unknown field ID ${fieldId - 1}") - field.read(dataStream) - fieldId = stream.readVarInt() - i++ - } - - return i - } - - private object Mark - - companion object { - private val ClearBacklogEntry = { stream: DataOutputStream -> stream.write(ChangesetAction.CLEAR.ordinal + 1) } - private val ChangesetActionList = ChangesetAction.values() - private val ClearMapChangeset = MapChangeset(ChangesetAction.CLEAR, null, null) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kommons/util/Delegate.kt b/src/main/kotlin/ru/dbotthepony/kommons/util/Delegate.kt index e55b7cf..db45866 100644 --- a/src/main/kotlin/ru/dbotthepony/kommons/util/Delegate.kt +++ b/src/main/kotlin/ru/dbotthepony/kommons/util/Delegate.kt @@ -4,7 +4,9 @@ package ru.dbotthepony.kommons.util import java.util.function.Consumer import java.util.function.Supplier +import kotlin.reflect.KMutableProperty0 import kotlin.reflect.KProperty +import kotlin.reflect.KProperty0 /** * [Supplier] and [Consumer] combined in single interface @@ -48,7 +50,11 @@ interface Delegate : Supplier, Consumer { } } - class Of(getter: Supplier, setter: Consumer) : Delegate, Supplier by getter, Consumer by setter + class Of(getter: Supplier, setter: Consumer) : Delegate, Supplier by getter, Consumer by setter { + constructor(prop: KMutableProperty0) : this(prop::get, prop::set) + @Deprecated("Warning: Using read-only property as delegate") + constructor(prop: KProperty0) : this(prop::get, Consumer { }) + } } operator fun Supplier.getValue(ref: Any?, property: KProperty<*>): V { diff --git a/src/main/kotlin/ru/dbotthepony/kommons/util/Listenable.kt b/src/main/kotlin/ru/dbotthepony/kommons/util/Listenable.kt index a40613c..d091c63 100644 --- a/src/main/kotlin/ru/dbotthepony/kommons/util/Listenable.kt +++ b/src/main/kotlin/ru/dbotthepony/kommons/util/Listenable.kt @@ -25,22 +25,21 @@ interface Listenable { */ class Impl : Listenable, Consumer { private inner class L(val callback: Consumer) : Listenable.L { - private var isRemoved = false - init { subscribers.add(this) } override fun remove() { - if (!isRemoved) { - isRemoved = true - subscribers.remove(this) - } + subscribers.remove(this) } } private val subscribers = CopyOnWriteArrayList() + fun clear() { + subscribers.clear() + } + override fun addListener(listener: Consumer): Listenable.L { return L(listener) } @@ -68,6 +67,10 @@ interface Listenable { private val subscribers = CopyOnWriteArrayList() + fun clear() { + subscribers.clear() + } + override fun addListener(listener: Runnable): Listenable.L { return L(listener) } diff --git a/src/main/kotlin/ru/dbotthepony/kommons/util/ListenableDelegate.kt b/src/main/kotlin/ru/dbotthepony/kommons/util/ListenableDelegate.kt index 4b8171f..2962162 100644 --- a/src/main/kotlin/ru/dbotthepony/kommons/util/ListenableDelegate.kt +++ b/src/main/kotlin/ru/dbotthepony/kommons/util/ListenableDelegate.kt @@ -4,6 +4,7 @@ import java.util.concurrent.locks.ReentrantLock import java.util.function.Consumer import java.util.function.Supplier import kotlin.concurrent.withLock +import kotlin.reflect.KProperty0 interface ListenableDelegate : Delegate, Listenable { class UnboundBox : ListenableDelegate { @@ -20,6 +21,10 @@ interface ListenableDelegate : Delegate, Listenable { return get as V } + fun clearListeners() { + listeners.clear() + } + override fun accept(t: V) { value = t } @@ -34,6 +39,10 @@ interface ListenableDelegate : Delegate, Listenable { return listeners.addListener(listener) } + fun clearListeners() { + listeners.clear() + } + override fun get(): V { return value } @@ -49,6 +58,10 @@ interface ListenableDelegate : Delegate, Listenable { class SmartBox(value: V, private val getter: DelegateGetter, private val setter: DelegateSetter) : ListenableDelegate { private val value = Box(value) + fun clearListeners() { + value.clearListeners() + } + override fun addListener(listener: Consumer): Listenable.L { return value.addListener(listener) } @@ -94,6 +107,10 @@ interface ListenableDelegate : Delegate, Listenable { return listeners.addListener(listener) } + fun clearListeners() { + listeners.clear() + } + override fun observe(): Boolean { if (shadow !== Companion) return false @@ -142,6 +159,8 @@ interface ListenableDelegate : Delegate, Listenable { } class Shadow(parent: Supplier) : AbstractShadow(parent) { + constructor(parent: KProperty0) : this(parent::get) + override fun compare(a: V, b: V): Boolean { return a == b } @@ -151,7 +170,9 @@ interface ListenableDelegate : Delegate, Listenable { } } - class SmartShadow(parent: Supplier, private val comparator: (V, V) -> Boolean, private val copy: (V) -> V) : AbstractShadow(parent) { + class CustomShadow(parent: Supplier, private val comparator: (V, V) -> Boolean, private val copy: (V) -> V) : AbstractShadow(parent) { + constructor(parent: KProperty0, comparator: (V, V) -> Boolean, copy: (V) -> V) : this(parent::get, comparator, copy) + override fun compare(a: V, b: V): Boolean { return comparator(a, b) }