DelegateSyncher, a vastly superior version of FieldSynchronizer
This commit is contained in:
parent
164ba29add
commit
80e5347ac6
@ -4,7 +4,7 @@ kotlin.code.style=official
|
|||||||
specifyKotlinAsDependency=false
|
specifyKotlinAsDependency=false
|
||||||
|
|
||||||
projectGroup=ru.dbotthepony.kommons
|
projectGroup=ru.dbotthepony.kommons
|
||||||
projectVersion=2.0.0
|
projectVersion=2.1.0
|
||||||
|
|
||||||
guavaDepVersion=33.0.0
|
guavaDepVersion=33.0.0
|
||||||
gsonDepVersion=2.8.9
|
gsonDepVersion=2.8.9
|
||||||
|
@ -52,14 +52,26 @@ val Vector4iCodec = StreamCodec.Impl(DataInputStream::readVector4i, DataOutputSt
|
|||||||
val Vector4dCodec = StreamCodec.Impl(DataInputStream::readVector4d, DataOutputStream::writeStruct4d)
|
val Vector4dCodec = StreamCodec.Impl(DataInputStream::readVector4d, DataOutputStream::writeStruct4d)
|
||||||
val Vector4fCodec = StreamCodec.Impl(DataInputStream::readVector4f, DataOutputStream::writeStruct4f)
|
val Vector4fCodec = StreamCodec.Impl(DataInputStream::readVector4f, DataOutputStream::writeStruct4f)
|
||||||
|
|
||||||
fun SynchedDelegates.vec2i(value: Vector2i, getter: DelegateGetter<Vector2i> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2i> = DelegateSetter.passthrough()): ListenableDelegate<Vector2i> = RegularValue(value, Vector2iCodec, getter, setter)
|
fun DelegateSyncher.vec2i(value: Vector2i, getter: DelegateGetter<Vector2i> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2i> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector2iCodec)
|
||||||
fun SynchedDelegates.vec2d(value: Vector2d, getter: DelegateGetter<Vector2d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2d> = DelegateSetter.passthrough()): ListenableDelegate<Vector2d> = RegularValue(value, Vector2dCodec, getter, setter)
|
fun DelegateSyncher.vec2d(value: Vector2d, getter: DelegateGetter<Vector2d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2d> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector2dCodec)
|
||||||
fun SynchedDelegates.vec2f(value: Vector2f, getter: DelegateGetter<Vector2f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2f> = DelegateSetter.passthrough()): ListenableDelegate<Vector2f> = RegularValue(value, Vector2fCodec, getter, setter)
|
fun DelegateSyncher.vec2f(value: Vector2f, getter: DelegateGetter<Vector2f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2f> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector2fCodec)
|
||||||
|
|
||||||
fun SynchedDelegates.vec3i(value: Vector3i, getter: DelegateGetter<Vector3i> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3i> = DelegateSetter.passthrough()): ListenableDelegate<Vector3i> = RegularValue(value, Vector3iCodec, getter, setter)
|
fun DelegateSyncher.vec3i(value: Vector3i, getter: DelegateGetter<Vector3i> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3i> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector3iCodec)
|
||||||
fun SynchedDelegates.vec3d(value: Vector3d, getter: DelegateGetter<Vector3d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3d> = DelegateSetter.passthrough()): ListenableDelegate<Vector3d> = RegularValue(value, Vector3dCodec, getter, setter)
|
fun DelegateSyncher.vec3d(value: Vector3d, getter: DelegateGetter<Vector3d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3d> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector3dCodec)
|
||||||
fun SynchedDelegates.vec3f(value: Vector3f, getter: DelegateGetter<Vector3f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3f> = DelegateSetter.passthrough()): ListenableDelegate<Vector3f> = RegularValue(value, Vector3fCodec, getter, setter)
|
fun DelegateSyncher.vec3f(value: Vector3f, getter: DelegateGetter<Vector3f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3f> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector3fCodec)
|
||||||
|
|
||||||
fun SynchedDelegates.vec4i(value: Vector4i, getter: DelegateGetter<Vector4i> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4i> = DelegateSetter.passthrough()): ListenableDelegate<Vector4i> = RegularValue(value, Vector4iCodec, getter, setter)
|
fun DelegateSyncher.vec4i(value: Vector4i, getter: DelegateGetter<Vector4i> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4i> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector4iCodec)
|
||||||
fun SynchedDelegates.vec4d(value: Vector4d, getter: DelegateGetter<Vector4d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4d> = DelegateSetter.passthrough()): ListenableDelegate<Vector4d> = RegularValue(value, Vector4dCodec, getter, setter)
|
fun DelegateSyncher.vec4d(value: Vector4d, getter: DelegateGetter<Vector4d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4d> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector4dCodec)
|
||||||
fun SynchedDelegates.vec4f(value: Vector4f, getter: DelegateGetter<Vector4f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4f> = DelegateSetter.passthrough()): ListenableDelegate<Vector4f> = RegularValue(value, Vector4fCodec, getter, setter)
|
fun DelegateSyncher.vec4f(value: Vector4f, getter: DelegateGetter<Vector4f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4f> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector4fCodec)
|
||||||
|
|
||||||
|
fun DelegateSyncher.Group.vec2i(value: Vector2i, getter: DelegateGetter<Vector2i> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2i> = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector2iCodec)
|
||||||
|
fun DelegateSyncher.Group.vec2d(value: Vector2d, getter: DelegateGetter<Vector2d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2d> = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector2dCodec)
|
||||||
|
fun DelegateSyncher.Group.vec2f(value: Vector2f, getter: DelegateGetter<Vector2f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2f> = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector2fCodec)
|
||||||
|
|
||||||
|
fun DelegateSyncher.Group.vec3i(value: Vector3i, getter: DelegateGetter<Vector3i> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3i> = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector3iCodec)
|
||||||
|
fun DelegateSyncher.Group.vec3d(value: Vector3d, getter: DelegateGetter<Vector3d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3d> = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector3dCodec)
|
||||||
|
fun DelegateSyncher.Group.vec3f(value: Vector3f, getter: DelegateGetter<Vector3f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3f> = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector3fCodec)
|
||||||
|
|
||||||
|
fun DelegateSyncher.Group.vec4i(value: Vector4i, getter: DelegateGetter<Vector4i> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4i> = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector4iCodec)
|
||||||
|
fun DelegateSyncher.Group.vec4d(value: Vector4d, getter: DelegateGetter<Vector4d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4d> = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector4dCodec)
|
||||||
|
fun DelegateSyncher.Group.vec4f(value: Vector4f, getter: DelegateGetter<Vector4f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4f> = DelegateSetter.passthrough()) = add(ListenableDelegate.maskSmart(value, getter, setter), Vector4fCodec)
|
||||||
|
@ -1,33 +1,73 @@
|
|||||||
package ru.dbotthepony.kommons.collect
|
package ru.dbotthepony.kommons.collect
|
||||||
|
|
||||||
abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = HashMap()) : MutableMap<K, V> {
|
import ru.dbotthepony.kommons.util.Listenable
|
||||||
protected abstract fun onClear()
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
protected abstract fun onValueAdded(key: K, value: V)
|
|
||||||
protected abstract fun onValueRemoved(key: K, value: V)
|
|
||||||
|
|
||||||
final override val size: Int
|
/**
|
||||||
|
* Proxies all operations to [backingMap] along with generating events for [MapListener]s
|
||||||
|
*/
|
||||||
|
open class ListenableMap<K, V>(protected val backingMap: MutableMap<K, V> = HashMap()) : MutableMap<K, V> {
|
||||||
|
interface MapListener<K, V> {
|
||||||
|
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<K, V>) : Listenable.L {
|
||||||
|
init {
|
||||||
|
listeners.add(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove() {
|
||||||
|
listeners.remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val listeners = CopyOnWriteArrayList<Listener>()
|
||||||
|
|
||||||
|
fun addListener(listener: MapListener<K, V>): Listenable.L {
|
||||||
|
return Listener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearListeners() {
|
||||||
|
listeners.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val size: Int
|
||||||
get() = backingMap.size
|
get() = backingMap.size
|
||||||
|
|
||||||
final override fun containsKey(key: K): Boolean {
|
override fun containsKey(key: K): Boolean {
|
||||||
return backingMap.containsKey(key)
|
return backingMap.containsKey(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
final override fun containsValue(value: V): Boolean {
|
override fun containsValue(value: V): Boolean {
|
||||||
return backingMap.containsValue(value)
|
return backingMap.containsValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
final override fun get(key: K): V? {
|
override fun get(key: K): V? {
|
||||||
return backingMap[key]
|
return backingMap[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
final override fun isEmpty(): Boolean {
|
override fun isEmpty(): Boolean {
|
||||||
return backingMap.isEmpty()
|
return backingMap.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
final override val entries: MutableSet<MutableMap.MutableEntry<K, V>> by lazy {
|
override val entries: MutableSet<MutableMap.MutableEntry<K, V>> by lazy {
|
||||||
object : MutableSet<MutableMap.MutableEntry<K, V>> {
|
object : MutableSet<MutableMap.MutableEntry<K, V>> {
|
||||||
override fun add(element: MutableMap.MutableEntry<K, V>): Boolean {
|
override fun add(element: MutableMap.MutableEntry<K, V>): Boolean {
|
||||||
this@ProxiedMap[element.key] = element.value
|
this@ListenableMap[element.key] = element.value
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,10 +80,10 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun clear() {
|
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<MutableMap.MutableEntry<K, V>> {
|
override fun iterator(): MutableIterator<MutableMap.MutableEntry<K, V>> {
|
||||||
return object : MutableIterator<MutableMap.MutableEntry<K, V>> {
|
return object : MutableIterator<MutableMap.MutableEntry<K, V>> {
|
||||||
@ -116,9 +156,9 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final override val keys: MutableSet<K> by lazy {
|
override val keys: MutableSet<K> by lazy {
|
||||||
object : MutableSet<K> {
|
object : MutableSet<K> {
|
||||||
val parent = this@ProxiedMap.backingMap.keys
|
val parent = this@ListenableMap.backingMap.keys
|
||||||
|
|
||||||
override fun add(element: K): Boolean {
|
override fun add(element: K): Boolean {
|
||||||
throw UnsupportedOperationException()
|
throw UnsupportedOperationException()
|
||||||
@ -129,7 +169,7 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun clear() {
|
override fun clear() {
|
||||||
this@ProxiedMap.clear()
|
this@ListenableMap.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iterator(): MutableIterator<K> {
|
override fun iterator(): MutableIterator<K> {
|
||||||
@ -147,7 +187,7 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
|
|||||||
|
|
||||||
override fun remove() {
|
override fun remove() {
|
||||||
val last = last ?: throw IllegalStateException("Never called next()")
|
val last = last ?: throw IllegalStateException("Never called next()")
|
||||||
val value = this@ProxiedMap[last] ?: throw ConcurrentModificationException()
|
val value = this@ListenableMap[last] ?: throw ConcurrentModificationException()
|
||||||
parentIterator.remove()
|
parentIterator.remove()
|
||||||
onValueRemoved(last, value)
|
onValueRemoved(last, value)
|
||||||
}
|
}
|
||||||
@ -155,7 +195,7 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(element: K): Boolean {
|
override fun remove(element: K): Boolean {
|
||||||
return this@ProxiedMap.remove(element) != null
|
return this@ListenableMap.remove(element) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeAll(elements: Collection<K>): Boolean {
|
override fun removeAll(elements: Collection<K>): Boolean {
|
||||||
@ -199,9 +239,9 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final override val values: MutableCollection<V> by lazy {
|
override val values: MutableCollection<V> by lazy {
|
||||||
object : MutableCollection<V> {
|
object : MutableCollection<V> {
|
||||||
private val parent = this@ProxiedMap.backingMap.values
|
private val parent = this@ListenableMap.backingMap.values
|
||||||
|
|
||||||
override val size: Int
|
override val size: Int
|
||||||
get() = parent.size
|
get() = parent.size
|
||||||
@ -227,7 +267,7 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun clear() {
|
override fun clear() {
|
||||||
this@ProxiedMap.clear()
|
this@ListenableMap.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iterator(): MutableIterator<V> {
|
override fun iterator(): MutableIterator<V> {
|
||||||
@ -249,8 +289,8 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(element: V): Boolean {
|
override fun remove(element: V): Boolean {
|
||||||
val indexOf = this@ProxiedMap.backingMap.firstNotNullOfOrNull { if (it.value == element) it.key else null } ?: return false
|
val indexOf = this@ListenableMap.backingMap.firstNotNullOfOrNull { if (it.value == element) it.key else null } ?: return false
|
||||||
this@ProxiedMap.remove(indexOf)
|
this@ListenableMap.remove(indexOf)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,7 +336,7 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
|
|||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
|
|
||||||
final override fun putAll(from: Map<out K, V>) {
|
override fun putAll(from: Map<out K, V>) {
|
||||||
for ((k, v) in from) {
|
for ((k, v) in from) {
|
||||||
this[k] = v
|
this[k] = v
|
||||||
}
|
}
|
141
src/main/kotlin/ru/dbotthepony/kommons/collect/ListenableSet.kt
Normal file
141
src/main/kotlin/ru/dbotthepony/kommons/collect/ListenableSet.kt
Normal file
@ -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<E>(protected val backingSet: MutableSet<E>) : MutableSet<E> {
|
||||||
|
interface SetListener<E> {
|
||||||
|
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<E>) : Listenable.L {
|
||||||
|
init {
|
||||||
|
listeners.add(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove() {
|
||||||
|
listeners.remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val listeners = CopyOnWriteArrayList<Listener>()
|
||||||
|
|
||||||
|
fun addListener(listener: SetListener<E>): 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<E>): 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<E>): Boolean {
|
||||||
|
var any = false
|
||||||
|
elements.forEach { any = remove(it) || any }
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun retainAll(elements: Collection<E>): 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<E>): 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<E> {
|
||||||
|
return object : MutableIterator<E> {
|
||||||
|
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
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
package ru.dbotthepony.kommons.io
|
|
||||||
|
|
||||||
enum class ChangesetAction {
|
|
||||||
CLEAR, ADD, REMOVE
|
|
||||||
}
|
|
@ -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)
|
|
768
src/main/kotlin/ru/dbotthepony/kommons/io/DelegateSyncher.kt
Normal file
768
src/main/kotlin/ru/dbotthepony/kommons/io/DelegateSyncher.kt
Normal file
@ -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<Unit>, Observer {
|
||||||
|
private val slots = ArrayList<AbstractSlot?>()
|
||||||
|
private val observers = CopyOnWriteArrayList<AbstractSlot>()
|
||||||
|
private val dirtySlots = ConcurrentLinkedQueue<AbstractSlot>()
|
||||||
|
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<Unit>): Listenable.L {
|
||||||
|
return dirtySubs.addListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun constructPayload(): FastByteArrayOutputStream? {
|
||||||
|
if (!isDirtyF.get()) return null
|
||||||
|
|
||||||
|
val slots = ObjectAVLTreeSet<AbstractSlot>()
|
||||||
|
|
||||||
|
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<AbstractSlot> {
|
||||||
|
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<V>(private val parent: ListenableDelegate<V>, private val codec: StreamCodec<V>) : AbstractSlot(), ListenableDelegate<V> {
|
||||||
|
private val subs = Listenable.Impl<V>()
|
||||||
|
|
||||||
|
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<V>): 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<V>(private val parent: Delegate<V>, private val codec: StreamCodec<V>) : AbstractSlot(), ListenableDelegate<V> {
|
||||||
|
private var lastValue: Any? = Mark
|
||||||
|
private val localLock = ReentrantLock() // avoid deadlock when code inside subscribers cause another value observation
|
||||||
|
private val subs = Listenable.Impl<V>()
|
||||||
|
|
||||||
|
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<V>): Listenable.L {
|
||||||
|
check(!isRemoved) { "This network slot was removed" }
|
||||||
|
return subs.addListener(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ComputedSlot<V>(parent: Supplier<V>, private val codec: StreamCodec<V>) : AbstractSlot(), ListenableDelegate<V> {
|
||||||
|
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<V>): Listenable.L {
|
||||||
|
check(!isRemoved) { "This network slot was removed" }
|
||||||
|
return shadow.addListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun actuallyRemove() {
|
||||||
|
shadow.clearListeners()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class SetSlot<E>(val value: ListenableSet<E>, private val codec: StreamCodec<E>) : AbstractSlot(), ListenableSet.SetListener<E>, Listenable<ListenableSet<E>> {
|
||||||
|
private val sub = value.addListener(this)
|
||||||
|
private val subs = Listenable.Impl<ListenableSet<E>>()
|
||||||
|
private val backlog = ArrayList<DataOutputStream.() -> 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<ListenableSet<E>>): Listenable.L {
|
||||||
|
check(!isRemoved) { "This network slot was removed" }
|
||||||
|
return subs.addListener(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class MapSlot<K, V>(val value: ListenableMap<K, V>, private val keyCodec: StreamCodec<K>, private val valueCodec: StreamCodec<V>) : AbstractSlot(), Listenable<ListenableMap<K, V>>, ListenableMap.MapListener<K, V> {
|
||||||
|
private val sub = value.addListener(this)
|
||||||
|
private val subs = Listenable.Impl<ListenableMap<K, V>>()
|
||||||
|
private val backlog = ArrayList<DataOutputStream.() -> 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<ListenableMap<K, V>>): 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 <V> add(delegate: ListenableDelegate<V>, codec: StreamCodec<V>): Slot<V> {
|
||||||
|
return Slot(delegate, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <V> add(delegate: Delegate<V>, codec: StreamCodec<V>): ObservedSlot<V> {
|
||||||
|
return ObservedSlot(delegate, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <V> add(delegate: Supplier<V>, codec: StreamCodec<V>): ComputedSlot<V> {
|
||||||
|
return ComputedSlot(delegate, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <E> add(delegate: ListenableSet<E>, codec: StreamCodec<E>): SetSlot<E> {
|
||||||
|
return SetSlot(delegate, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K, V> add(delegate: ListenableMap<K, V>, keyCodec: StreamCodec<K>, valueCodec: StreamCodec<V>): MapSlot<K, V> {
|
||||||
|
return MapSlot(delegate, keyCodec, valueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vbyte")
|
||||||
|
@JvmOverloads
|
||||||
|
fun byte(value: Byte = 0, getter: DelegateGetter<Byte> = DelegateGetter.passthrough(), setter: DelegateSetter<Byte> = DelegateSetter.passthrough()): Slot<Byte> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), ByteValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vshort")
|
||||||
|
@JvmOverloads
|
||||||
|
fun short(value: Short = 0, getter: DelegateGetter<Short> = DelegateGetter.passthrough(), setter: DelegateSetter<Short> = DelegateSetter.passthrough()): Slot<Short> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), ShortValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vchar")
|
||||||
|
@JvmOverloads
|
||||||
|
fun char(value: Char, getter: DelegateGetter<Char> = DelegateGetter.passthrough(), setter: DelegateSetter<Char> = DelegateSetter.passthrough()): Slot<Char> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), CharValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vint")
|
||||||
|
@JvmOverloads
|
||||||
|
fun int(value: Int = 0, getter: DelegateGetter<Int> = DelegateGetter.passthrough(), setter: DelegateSetter<Int> = DelegateSetter.passthrough()): Slot<Int> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), VarIntValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vlong")
|
||||||
|
@JvmOverloads
|
||||||
|
fun long(value: Long = 0L, getter: DelegateGetter<Long> = DelegateGetter.passthrough(), setter: DelegateSetter<Long> = DelegateSetter.passthrough()): Slot<Long> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), VarLongValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vfloat")
|
||||||
|
@JvmOverloads
|
||||||
|
fun float(value: Float = 0f, getter: DelegateGetter<Float> = DelegateGetter.passthrough(), setter: DelegateSetter<Float> = DelegateSetter.passthrough()): Slot<Float> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), FloatValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vdouble")
|
||||||
|
@JvmOverloads
|
||||||
|
fun double(value: Double = 0.0, getter: DelegateGetter<Double> = DelegateGetter.passthrough(), setter: DelegateSetter<Double> = DelegateSetter.passthrough()): Slot<Double> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), DoubleValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vboolean")
|
||||||
|
@JvmOverloads
|
||||||
|
fun boolean(value: Boolean = false, getter: DelegateGetter<Boolean> = DelegateGetter.passthrough(), setter: DelegateSetter<Boolean> = DelegateSetter.passthrough()): Slot<Boolean> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), BooleanValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun string(value: String, getter: DelegateGetter<String> = DelegateGetter.passthrough(), setter: DelegateSetter<String> = DelegateSetter.passthrough()): Slot<String> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), BinaryStringCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun uuid(value: UUID, getter: DelegateGetter<UUID> = DelegateGetter.passthrough(), setter: DelegateSetter<UUID> = DelegateSetter.passthrough()): Slot<UUID> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), UUIDValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun decimal(value: Decimal = Decimal.ZERO, getter: DelegateGetter<Decimal> = DelegateGetter.passthrough(), setter: DelegateSetter<Decimal> = DelegateSetter.passthrough()): Slot<Decimal> {
|
||||||
|
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<DelegateSyncher.() -> Unit>()
|
||||||
|
|
||||||
|
fun <D : ListenableDelegate<V>, V> add(delegate: D, codec: StreamCodec<V>): D {
|
||||||
|
constructors.add {
|
||||||
|
add(delegate, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
return delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <D : Delegate<V>, V> add(delegate: D, codec: StreamCodec<V>): D {
|
||||||
|
constructors.add {
|
||||||
|
add(delegate, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
return delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <D : ListenableSet<E>, E> add(delegate: D, codec: StreamCodec<E>): D {
|
||||||
|
constructors.add {
|
||||||
|
add(delegate, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
return delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <D : ListenableMap<K, V>, K, V> add(delegate: D, keyCodec: StreamCodec<K>, valueCodec: StreamCodec<V>): D {
|
||||||
|
constructors.add {
|
||||||
|
add(delegate, keyCodec, valueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
return delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vbyte")
|
||||||
|
@JvmOverloads
|
||||||
|
fun byte(value: Byte = 0, getter: DelegateGetter<Byte> = DelegateGetter.passthrough(), setter: DelegateSetter<Byte> = DelegateSetter.passthrough()): ListenableDelegate<Byte> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), ByteValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vshort")
|
||||||
|
@JvmOverloads
|
||||||
|
fun short(value: Short = 0, getter: DelegateGetter<Short> = DelegateGetter.passthrough(), setter: DelegateSetter<Short> = DelegateSetter.passthrough()): ListenableDelegate<Short> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), ShortValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vchar")
|
||||||
|
@JvmOverloads
|
||||||
|
fun char(value: Char, getter: DelegateGetter<Char> = DelegateGetter.passthrough(), setter: DelegateSetter<Char> = DelegateSetter.passthrough()): ListenableDelegate<Char> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), CharValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vint")
|
||||||
|
@JvmOverloads
|
||||||
|
fun int(value: Int = 0, getter: DelegateGetter<Int> = DelegateGetter.passthrough(), setter: DelegateSetter<Int> = DelegateSetter.passthrough()): ListenableDelegate<Int> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), VarIntValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vlong")
|
||||||
|
@JvmOverloads
|
||||||
|
fun long(value: Long = 0L, getter: DelegateGetter<Long> = DelegateGetter.passthrough(), setter: DelegateSetter<Long> = DelegateSetter.passthrough()): ListenableDelegate<Long> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), VarLongValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vfloat")
|
||||||
|
@JvmOverloads
|
||||||
|
fun float(value: Float = 0f, getter: DelegateGetter<Float> = DelegateGetter.passthrough(), setter: DelegateSetter<Float> = DelegateSetter.passthrough()): ListenableDelegate<Float> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), FloatValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vdouble")
|
||||||
|
@JvmOverloads
|
||||||
|
fun double(value: Double = 0.0, getter: DelegateGetter<Double> = DelegateGetter.passthrough(), setter: DelegateSetter<Double> = DelegateSetter.passthrough()): ListenableDelegate<Double> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), DoubleValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("vboolean")
|
||||||
|
@JvmOverloads
|
||||||
|
fun boolean(value: Boolean = false, getter: DelegateGetter<Boolean> = DelegateGetter.passthrough(), setter: DelegateSetter<Boolean> = DelegateSetter.passthrough()): ListenableDelegate<Boolean> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), BooleanValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun string(value: String, getter: DelegateGetter<String> = DelegateGetter.passthrough(), setter: DelegateSetter<String> = DelegateSetter.passthrough()): ListenableDelegate<String> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), BinaryStringCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun uuid(value: UUID, getter: DelegateGetter<UUID> = DelegateGetter.passthrough(), setter: DelegateSetter<UUID> = DelegateSetter.passthrough()): ListenableDelegate<UUID> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), UUIDValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun decimal(value: Decimal = Decimal.ZERO, getter: DelegateGetter<Decimal> = DelegateGetter.passthrough(), setter: DelegateSetter<Decimal> = DelegateSetter.passthrough()): ListenableDelegate<Decimal> {
|
||||||
|
return add(ListenableDelegate.maskSmart(value, getter, setter), DecimalValueCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <E> set(codec: StreamCodec<E>, backing: MutableSet<E> = ObjectOpenHashSet()): ListenableSet<E> {
|
||||||
|
val delegate = ListenableSet(backing)
|
||||||
|
constructors.add { add(delegate, codec) }
|
||||||
|
return delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K, V> map(keyCodec: StreamCodec<K>, valueCodec: StreamCodec<V>, backing: MutableMap<K, V> = Object2ObjectOpenHashMap()): ListenableMap<K, V> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kommons.io
|
package ru.dbotthepony.kommons.io
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||||
|
import ru.dbotthepony.kommons.math.Decimal
|
||||||
import java.io.DataInput
|
import java.io.DataInput
|
||||||
import java.io.EOFException
|
import java.io.EOFException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -210,3 +211,11 @@ fun InputStream.readUTF(): String {
|
|||||||
fun InputStream.readUUID(): UUID {
|
fun InputStream.readUUID(): UUID {
|
||||||
return UUID(readLong(), readLong())
|
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)
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
package ru.dbotthepony.kommons.io
|
|
||||||
|
|
||||||
data class MapChangeset<out K, out V>(
|
|
||||||
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!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kommons.io
|
package ru.dbotthepony.kommons.io
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.math.Decimal
|
||||||
import ru.dbotthepony.kommons.util.IStruct2b
|
import ru.dbotthepony.kommons.util.IStruct2b
|
||||||
import ru.dbotthepony.kommons.util.IStruct2d
|
import ru.dbotthepony.kommons.util.IStruct2d
|
||||||
import ru.dbotthepony.kommons.util.IStruct2f
|
import ru.dbotthepony.kommons.util.IStruct2f
|
||||||
@ -250,3 +251,9 @@ fun OutputStream.writeStruct4b(value: IStruct4b) {
|
|||||||
writeShort(value.component3().toInt() and 0xFF)
|
writeShort(value.component3().toInt() and 0xFF)
|
||||||
writeShort(value.component4().toInt() and 0xFF)
|
writeShort(value.component4().toInt() and 0xFF)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun OutputStream.writeDecimal(value: Decimal) {
|
||||||
|
val bytes = value.toByteArray()
|
||||||
|
writeVarInt(bytes.size)
|
||||||
|
write(bytes)
|
||||||
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package ru.dbotthepony.kommons.io
|
|
||||||
|
|
||||||
data class SetChangeset<out V>(
|
|
||||||
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!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,22 +7,18 @@ import kotlin.reflect.KClass
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents value which can be encoded onto or decoded from stream.
|
* Represents value which can be encoded onto or decoded from stream.
|
||||||
*
|
|
||||||
* Also provides [copy] and [compare] methods
|
|
||||||
*/
|
*/
|
||||||
interface StreamCodec<V> {
|
interface StreamCodec<V> {
|
||||||
fun read(stream: DataInputStream): V
|
fun read(stream: DataInputStream): V
|
||||||
fun write(stream: DataOutputStream, value: V)
|
fun write(stream: DataOutputStream, value: V)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* defensive copy
|
* Defensive copy
|
||||||
*/
|
*/
|
||||||
fun copy(value: V): V
|
fun copy(value: V): V
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional equality check override. Utilized to determine whenever e.g. network value is different from new value
|
* Optional custom equality check
|
||||||
*
|
|
||||||
* By default uses [Any.equals]
|
|
||||||
*/
|
*/
|
||||||
fun compare(a: V, b: V): Boolean {
|
fun compare(a: V, b: V): Boolean {
|
||||||
return a == b
|
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 VarIntValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarInt, DataOutputStream::writeSignedVarInt)
|
||||||
val VarLongValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarLong, DataOutputStream::writeSignedVarLong)
|
val VarLongValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarLong, DataOutputStream::writeSignedVarLong)
|
||||||
val BinaryStringCodec = StreamCodec.Impl(DataInputStream::readBinaryString, DataOutputStream::writeBinaryString)
|
val BinaryStringCodec = StreamCodec.Impl(DataInputStream::readBinaryString, DataOutputStream::writeBinaryString)
|
||||||
|
val DecimalValueCodec = StreamCodec.Impl(DataInputStream::readDecimal, DataOutputStream::writeDecimal)
|
||||||
|
|
||||||
fun <E : Enum<E>> Class<E>.codec() = StreamCodec.Enum(this)
|
fun <E : Enum<E>> Class<E>.codec() = StreamCodec.Enum(this)
|
||||||
fun <E : Enum<E>> KClass<E>.codec() = StreamCodec.Enum(this.java)
|
fun <E : Enum<E>> KClass<E>.codec() = StreamCodec.Enum(this.java)
|
||||||
|
@ -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<AbstractValue?>(0)
|
|
||||||
private val observers = ArrayList<Observer>(0)
|
|
||||||
private val endpoints = ArrayList<WeakReference<Endpoint>>(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<Any, Endpoint>())
|
|
||||||
|
|
||||||
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<AbstractValue>(4)
|
|
||||||
|
|
||||||
// use LinkedList because it is ensured memory is freed on LinkedList#clear
|
|
||||||
private val mapBacklogs = Reference2ObjectOpenHashMap<Map<*, *>, LinkedList<Pair<Any?, (DataOutputStream) -> Unit>>>()
|
|
||||||
private val setBacklogs = Reference2ObjectOpenHashMap<Set<*>, LinkedList<Pair<Any?, (DataOutputStream) -> 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 <K, V> getMapBacklog(map: Map<K, V>): LinkedList<Pair<Any?, (DataOutputStream) -> Unit>> {
|
|
||||||
if (isDisabled) return LinkedList()
|
|
||||||
|
|
||||||
return mapBacklogs.computeIfAbsent(map, Reference2ObjectFunction {
|
|
||||||
LinkedList()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun <K, V> removeMapBacklog(map: Map<K, V>) {
|
|
||||||
mapBacklogs.remove(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun <V> getSetBacklog(set: Set<V>): LinkedList<Pair<Any?, (DataOutputStream) -> Unit>> {
|
|
||||||
if (isDisabled) return LinkedList()
|
|
||||||
|
|
||||||
return setBacklogs.computeIfAbsent(set, Reference2ObjectFunction {
|
|
||||||
LinkedList()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun <V> removeSetBacklog(set: Set<V>) {
|
|
||||||
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<T>(
|
|
||||||
value: T,
|
|
||||||
private val codec: StreamCodec<T>,
|
|
||||||
getter: DelegateGetter<T> = DelegateGetter.passthrough(),
|
|
||||||
setter: DelegateSetter<T> = DelegateSetter.passthrough()
|
|
||||||
) : AbstractValue(), ListenableDelegate<T> 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<T>(
|
|
||||||
value: T,
|
|
||||||
private val codec: StreamCodec<T>,
|
|
||||||
getter: DelegateGetter<T> = DelegateGetter.passthrough(),
|
|
||||||
setter: DelegateSetter<T> = DelegateSetter.passthrough()
|
|
||||||
) : AbstractValue(), ListenableDelegate<T> 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<T>(getter: Supplier<T>, private val codec: StreamCodec<T>) : AbstractValue(), ListenableDelegate<T> {
|
|
||||||
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<T>): 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<Byte> = DelegateGetter.passthrough(), setter: DelegateSetter<Byte> = DelegateSetter.passthrough()): ListenableDelegate<Byte> = RegularValue(value, ByteValueCodec, getter, setter)
|
|
||||||
@JvmName("vshort") fun short(value: Short = 0, getter: DelegateGetter<Short> = DelegateGetter.passthrough(), setter: DelegateSetter<Short> = DelegateSetter.passthrough()): ListenableDelegate<Short> = RegularValue(value, ShortValueCodec, getter, setter)
|
|
||||||
@JvmName("vchar") fun char(value: Char = 0.toChar(), getter: DelegateGetter<Char> = DelegateGetter.passthrough(), setter: DelegateSetter<Char> = DelegateSetter.passthrough()): ListenableDelegate<Char> = RegularValue(value, CharValueCodec, getter, setter)
|
|
||||||
@JvmName("vint") fun int(value: Int = 0, getter: DelegateGetter<Int> = DelegateGetter.passthrough(), setter: DelegateSetter<Int> = DelegateSetter.passthrough()): ListenableDelegate<Int> = RegularValue(value, IntValueCodec, getter, setter)
|
|
||||||
@JvmName("vlong") fun long(value: Long = 0L, getter: DelegateGetter<Long> = DelegateGetter.passthrough(), setter: DelegateSetter<Long> = DelegateSetter.passthrough()): ListenableDelegate<Long> = RegularValue(value, LongValueCodec, getter, setter)
|
|
||||||
@JvmName("vfloat") fun float(value: Float = 0f, getter: DelegateGetter<Float> = DelegateGetter.passthrough(), setter: DelegateSetter<Float> = DelegateSetter.passthrough()): ListenableDelegate<Float> = RegularValue(value, FloatValueCodec, getter, setter)
|
|
||||||
@JvmName("vdouble") fun double(value: Double = 0.0, getter: DelegateGetter<Double> = DelegateGetter.passthrough(), setter: DelegateSetter<Double> = DelegateSetter.passthrough()): ListenableDelegate<Double> = RegularValue(value, DoubleValueCodec, getter, setter)
|
|
||||||
@JvmName("vboolean") fun boolean(value: Boolean = false, getter: DelegateGetter<Boolean> = DelegateGetter.passthrough(), setter: DelegateSetter<Boolean> = DelegateSetter.passthrough()): ListenableDelegate<Boolean> = RegularValue(value, BooleanValueCodec, getter, setter)
|
|
||||||
|
|
||||||
fun string(value: String, getter: DelegateGetter<String> = DelegateGetter.passthrough(), setter: DelegateSetter<String> = DelegateSetter.passthrough()): ListenableDelegate<String> = RegularValue(value, BinaryStringCodec, getter, setter)
|
|
||||||
fun uuid(value: UUID, getter: DelegateGetter<UUID> = DelegateGetter.passthrough(), setter: DelegateSetter<UUID> = DelegateSetter.passthrough()): ListenableDelegate<UUID> = RegularValue(value, UUIDValueCodec, getter, setter)
|
|
||||||
|
|
||||||
inner class Set<E>(
|
|
||||||
private val codec: StreamCodec<E>,
|
|
||||||
private val backingSet: MutableSet<E>,
|
|
||||||
private val callback: ((changes: Collection<SetChangeset<E>>) -> 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<MutableList<Pair<Any?, (DataOutputStream) -> 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<Any?, (DataOutputStream) -> 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<E> = object : MutableSet<E> {
|
|
||||||
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<E>): 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<E> {
|
|
||||||
return object : MutableIterator<E> {
|
|
||||||
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<E>): Boolean {
|
|
||||||
var any = false
|
|
||||||
elements.forEach { any = remove(it) || any }
|
|
||||||
return any
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun retainAll(elements: Collection<E>): 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<E>): 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<SetChangeset<E>>()
|
|
||||||
|
|
||||||
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<K, V>(
|
|
||||||
private val keyCodec: StreamCodec<K>,
|
|
||||||
private val valueCodec: StreamCodec<V>,
|
|
||||||
private val backingMap: MutableMap<K, V>,
|
|
||||||
private val callback: ((changes: Collection<MapChangeset<K, V>>) -> 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<LinkedList<Pair<Any?, (DataOutputStream) -> 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<K, V> = object : ProxiedMap<K, V>(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<MapChangeset<K, V>>()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,9 @@ package ru.dbotthepony.kommons.util
|
|||||||
|
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
|
import kotlin.reflect.KMutableProperty0
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.reflect.KProperty0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Supplier] and [Consumer] combined in single interface
|
* [Supplier] and [Consumer] combined in single interface
|
||||||
@ -48,7 +50,11 @@ interface Delegate<V> : Supplier<V>, Consumer<V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Of<V>(getter: Supplier<V>, setter: Consumer<V>) : Delegate<V>, Supplier<V> by getter, Consumer<V> by setter
|
class Of<V>(getter: Supplier<V>, setter: Consumer<V>) : Delegate<V>, Supplier<V> by getter, Consumer<V> by setter {
|
||||||
|
constructor(prop: KMutableProperty0<V>) : this(prop::get, prop::set)
|
||||||
|
@Deprecated("Warning: Using read-only property as delegate")
|
||||||
|
constructor(prop: KProperty0<V>) : this(prop::get, Consumer { })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun <V> Supplier<V>.getValue(ref: Any?, property: KProperty<*>): V {
|
operator fun <V> Supplier<V>.getValue(ref: Any?, property: KProperty<*>): V {
|
||||||
|
@ -25,22 +25,21 @@ interface Listenable<V> {
|
|||||||
*/
|
*/
|
||||||
class Impl<V> : Listenable<V>, Consumer<V> {
|
class Impl<V> : Listenable<V>, Consumer<V> {
|
||||||
private inner class L(val callback: Consumer<V>) : Listenable.L {
|
private inner class L(val callback: Consumer<V>) : Listenable.L {
|
||||||
private var isRemoved = false
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
subscribers.add(this)
|
subscribers.add(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun remove() {
|
override fun remove() {
|
||||||
if (!isRemoved) {
|
subscribers.remove(this)
|
||||||
isRemoved = true
|
|
||||||
subscribers.remove(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val subscribers = CopyOnWriteArrayList<L>()
|
private val subscribers = CopyOnWriteArrayList<L>()
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
subscribers.clear()
|
||||||
|
}
|
||||||
|
|
||||||
override fun addListener(listener: Consumer<V>): Listenable.L {
|
override fun addListener(listener: Consumer<V>): Listenable.L {
|
||||||
return L(listener)
|
return L(listener)
|
||||||
}
|
}
|
||||||
@ -68,6 +67,10 @@ interface Listenable<V> {
|
|||||||
|
|
||||||
private val subscribers = CopyOnWriteArrayList<L>()
|
private val subscribers = CopyOnWriteArrayList<L>()
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
subscribers.clear()
|
||||||
|
}
|
||||||
|
|
||||||
override fun addListener(listener: Runnable): Listenable.L {
|
override fun addListener(listener: Runnable): Listenable.L {
|
||||||
return L(listener)
|
return L(listener)
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import java.util.concurrent.locks.ReentrantLock
|
|||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
import kotlin.concurrent.withLock
|
import kotlin.concurrent.withLock
|
||||||
|
import kotlin.reflect.KProperty0
|
||||||
|
|
||||||
interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
|
interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
|
||||||
class UnboundBox<V> : ListenableDelegate<V> {
|
class UnboundBox<V> : ListenableDelegate<V> {
|
||||||
@ -20,6 +21,10 @@ interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
|
|||||||
return get as V
|
return get as V
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearListeners() {
|
||||||
|
listeners.clear()
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(t: V) {
|
override fun accept(t: V) {
|
||||||
value = t
|
value = t
|
||||||
}
|
}
|
||||||
@ -34,6 +39,10 @@ interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
|
|||||||
return listeners.addListener(listener)
|
return listeners.addListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearListeners() {
|
||||||
|
listeners.clear()
|
||||||
|
}
|
||||||
|
|
||||||
override fun get(): V {
|
override fun get(): V {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@ -49,6 +58,10 @@ interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
|
|||||||
class SmartBox<V>(value: V, private val getter: DelegateGetter<V>, private val setter: DelegateSetter<V>) : ListenableDelegate<V> {
|
class SmartBox<V>(value: V, private val getter: DelegateGetter<V>, private val setter: DelegateSetter<V>) : ListenableDelegate<V> {
|
||||||
private val value = Box(value)
|
private val value = Box(value)
|
||||||
|
|
||||||
|
fun clearListeners() {
|
||||||
|
value.clearListeners()
|
||||||
|
}
|
||||||
|
|
||||||
override fun addListener(listener: Consumer<V>): Listenable.L {
|
override fun addListener(listener: Consumer<V>): Listenable.L {
|
||||||
return value.addListener(listener)
|
return value.addListener(listener)
|
||||||
}
|
}
|
||||||
@ -94,6 +107,10 @@ interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
|
|||||||
return listeners.addListener(listener)
|
return listeners.addListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearListeners() {
|
||||||
|
listeners.clear()
|
||||||
|
}
|
||||||
|
|
||||||
override fun observe(): Boolean {
|
override fun observe(): Boolean {
|
||||||
if (shadow !== Companion)
|
if (shadow !== Companion)
|
||||||
return false
|
return false
|
||||||
@ -142,6 +159,8 @@ interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Shadow<V>(parent: Supplier<V>) : AbstractShadow<V>(parent) {
|
class Shadow<V>(parent: Supplier<V>) : AbstractShadow<V>(parent) {
|
||||||
|
constructor(parent: KProperty0<V>) : this(parent::get)
|
||||||
|
|
||||||
override fun compare(a: V, b: V): Boolean {
|
override fun compare(a: V, b: V): Boolean {
|
||||||
return a == b
|
return a == b
|
||||||
}
|
}
|
||||||
@ -151,7 +170,9 @@ interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SmartShadow<V>(parent: Supplier<V>, private val comparator: (V, V) -> Boolean, private val copy: (V) -> V) : AbstractShadow<V>(parent) {
|
class CustomShadow<V>(parent: Supplier<V>, private val comparator: (V, V) -> Boolean, private val copy: (V) -> V) : AbstractShadow<V>(parent) {
|
||||||
|
constructor(parent: KProperty0<V>, comparator: (V, V) -> Boolean, copy: (V) -> V) : this(parent::get, comparator, copy)
|
||||||
|
|
||||||
override fun compare(a: V, b: V): Boolean {
|
override fun compare(a: V, b: V): Boolean {
|
||||||
return comparator(a, b)
|
return comparator(a, b)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user