DelegateSyncher, a vastly superior version of FieldSynchronizer

This commit is contained in:
DBotThePony 2024-02-10 15:37:17 +07:00
parent 164ba29add
commit 80e5347ac6
Signed by: DBot
GPG Key ID: DCC23B5715498507
16 changed files with 1052 additions and 1043 deletions

View File

@ -4,7 +4,7 @@ kotlin.code.style=official
specifyKotlinAsDependency=false
projectGroup=ru.dbotthepony.kommons
projectVersion=2.0.0
projectVersion=2.1.0
guavaDepVersion=33.0.0
gsonDepVersion=2.8.9

View File

@ -52,14 +52,26 @@ val Vector4iCodec = StreamCodec.Impl(DataInputStream::readVector4i, DataOutputSt
val Vector4dCodec = StreamCodec.Impl(DataInputStream::readVector4d, DataOutputStream::writeStruct4d)
val Vector4fCodec = StreamCodec.Impl(DataInputStream::readVector4f, DataOutputStream::writeStruct4f)
fun SynchedDelegates.vec2i(value: Vector2i, getter: DelegateGetter<Vector2i> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2i> = DelegateSetter.passthrough()): ListenableDelegate<Vector2i> = RegularValue(value, Vector2iCodec, getter, setter)
fun SynchedDelegates.vec2d(value: Vector2d, getter: DelegateGetter<Vector2d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2d> = DelegateSetter.passthrough()): ListenableDelegate<Vector2d> = RegularValue(value, Vector2dCodec, getter, setter)
fun SynchedDelegates.vec2f(value: Vector2f, getter: DelegateGetter<Vector2f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2f> = DelegateSetter.passthrough()): ListenableDelegate<Vector2f> = RegularValue(value, Vector2fCodec, 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 DelegateSyncher.vec2d(value: Vector2d, getter: DelegateGetter<Vector2d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector2d> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector2dCodec)
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 SynchedDelegates.vec3d(value: Vector3d, getter: DelegateGetter<Vector3d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3d> = DelegateSetter.passthrough()): ListenableDelegate<Vector3d> = RegularValue(value, Vector3dCodec, getter, setter)
fun SynchedDelegates.vec3f(value: Vector3f, getter: DelegateGetter<Vector3f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3f> = DelegateSetter.passthrough()): ListenableDelegate<Vector3f> = RegularValue(value, Vector3fCodec, 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 DelegateSyncher.vec3d(value: Vector3d, getter: DelegateGetter<Vector3d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector3d> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector3dCodec)
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 SynchedDelegates.vec4d(value: Vector4d, getter: DelegateGetter<Vector4d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4d> = DelegateSetter.passthrough()): ListenableDelegate<Vector4d> = RegularValue(value, Vector4dCodec, getter, setter)
fun SynchedDelegates.vec4f(value: Vector4f, getter: DelegateGetter<Vector4f> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4f> = DelegateSetter.passthrough()): ListenableDelegate<Vector4f> = RegularValue(value, Vector4fCodec, 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 DelegateSyncher.vec4d(value: Vector4d, getter: DelegateGetter<Vector4d> = DelegateGetter.passthrough(), setter: DelegateSetter<Vector4d> = DelegateSetter.passthrough()) = Slot(ListenableDelegate.maskSmart(value, getter, setter), Vector4dCodec)
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)

View File

@ -1,33 +1,73 @@
package ru.dbotthepony.kommons.collect
abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = HashMap()) : MutableMap<K, V> {
protected abstract fun onClear()
protected abstract fun onValueAdded(key: K, value: V)
protected abstract fun onValueRemoved(key: K, value: V)
import ru.dbotthepony.kommons.util.Listenable
import java.util.concurrent.CopyOnWriteArrayList
final override val size: Int
/**
* Proxies all operations to [backingMap] along with generating events for [MapListener]s
*/
open class ListenableMap<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
final override fun containsKey(key: K): Boolean {
override fun containsKey(key: K): Boolean {
return backingMap.containsKey(key)
}
final override fun containsValue(value: V): Boolean {
override fun containsValue(value: V): Boolean {
return backingMap.containsValue(value)
}
final override fun get(key: K): V? {
override fun get(key: K): V? {
return backingMap[key]
}
final override fun isEmpty(): Boolean {
override fun isEmpty(): Boolean {
return backingMap.isEmpty()
}
final override val entries: MutableSet<MutableMap.MutableEntry<K, V>> by lazy {
override val entries: MutableSet<MutableMap.MutableEntry<K, V>> by lazy {
object : MutableSet<MutableMap.MutableEntry<K, V>> {
override fun add(element: MutableMap.MutableEntry<K, V>): Boolean {
this@ProxiedMap[element.key] = element.value
this@ListenableMap[element.key] = element.value
return true
}
@ -40,10 +80,10 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
}
override fun clear() {
this@ProxiedMap.clear()
this@ListenableMap.clear()
}
private val setParent = this@ProxiedMap.backingMap.entries
private val setParent = this@ListenableMap.backingMap.entries
override fun iterator(): MutableIterator<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> {
val parent = this@ProxiedMap.backingMap.keys
val parent = this@ListenableMap.backingMap.keys
override fun add(element: K): Boolean {
throw UnsupportedOperationException()
@ -129,7 +169,7 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
}
override fun clear() {
this@ProxiedMap.clear()
this@ListenableMap.clear()
}
override fun iterator(): MutableIterator<K> {
@ -147,7 +187,7 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
override fun remove() {
val last = last ?: throw IllegalStateException("Never called next()")
val value = this@ProxiedMap[last] ?: throw ConcurrentModificationException()
val value = this@ListenableMap[last] ?: throw ConcurrentModificationException()
parentIterator.remove()
onValueRemoved(last, value)
}
@ -155,7 +195,7 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
}
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 {
@ -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> {
private val parent = this@ProxiedMap.backingMap.values
private val parent = this@ListenableMap.backingMap.values
override val size: Int
get() = parent.size
@ -227,7 +267,7 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
}
override fun clear() {
this@ProxiedMap.clear()
this@ListenableMap.clear()
}
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 {
val indexOf = this@ProxiedMap.backingMap.firstNotNullOfOrNull { if (it.value == element) it.key else null } ?: return false
this@ProxiedMap.remove(indexOf)
val indexOf = this@ListenableMap.backingMap.firstNotNullOfOrNull { if (it.value == element) it.key else null } ?: return false
this@ListenableMap.remove(indexOf)
return true
}
@ -296,7 +336,7 @@ abstract class ProxiedMap<K, V>(protected val backingMap: MutableMap<K, V> = Has
return existing
}
final override fun putAll(from: Map<out K, V>) {
override fun putAll(from: Map<out K, V>) {
for ((k, v) in from) {
this[k] = v
}

View 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
}

View File

@ -1,5 +0,0 @@
package ru.dbotthepony.kommons.io
enum class ChangesetAction {
CLEAR, ADD, REMOVE
}

View File

@ -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)

View 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
}
}

View File

@ -1,6 +1,7 @@
package ru.dbotthepony.kommons.io
import it.unimi.dsi.fastutil.bytes.ByteArrayList
import ru.dbotthepony.kommons.math.Decimal
import java.io.DataInput
import java.io.EOFException
import java.io.IOException
@ -210,3 +211,11 @@ fun InputStream.readUTF(): String {
fun InputStream.readUUID(): UUID {
return UUID(readLong(), readLong())
}
fun InputStream.readDecimal(): Decimal {
val size = readVarInt()
require(size >= 0) { "Negative payload size: $size" }
val bytes = ByteArray(size)
read(bytes)
return Decimal.fromByteArray(bytes)
}

View File

@ -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!!)
}
}
}

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kommons.io
import ru.dbotthepony.kommons.math.Decimal
import ru.dbotthepony.kommons.util.IStruct2b
import ru.dbotthepony.kommons.util.IStruct2d
import ru.dbotthepony.kommons.util.IStruct2f
@ -250,3 +251,9 @@ fun OutputStream.writeStruct4b(value: IStruct4b) {
writeShort(value.component3().toInt() and 0xFF)
writeShort(value.component4().toInt() and 0xFF)
}
fun OutputStream.writeDecimal(value: Decimal) {
val bytes = value.toByteArray()
writeVarInt(bytes.size)
write(bytes)
}

View File

@ -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!!)
}
}
}

View File

@ -7,22 +7,18 @@ import kotlin.reflect.KClass
/**
* Represents value which can be encoded onto or decoded from stream.
*
* Also provides [copy] and [compare] methods
*/
interface StreamCodec<V> {
fun read(stream: DataInputStream): V
fun write(stream: DataOutputStream, value: V)
/**
* defensive copy
* Defensive copy
*/
fun copy(value: V): V
/**
* Optional equality check override. Utilized to determine whenever e.g. network value is different from new value
*
* By default uses [Any.equals]
* Optional custom equality check
*/
fun compare(a: V, b: V): Boolean {
return a == b
@ -170,6 +166,7 @@ val UUIDValueCodec = StreamCodec.Impl({ s -> UUID(s.readLong(), s.readLong()) },
val VarIntValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarInt, DataOutputStream::writeSignedVarInt)
val VarLongValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarLong, DataOutputStream::writeSignedVarLong)
val BinaryStringCodec = StreamCodec.Impl(DataInputStream::readBinaryString, DataOutputStream::writeBinaryString)
val DecimalValueCodec = StreamCodec.Impl(DataInputStream::readDecimal, DataOutputStream::writeDecimal)
fun <E : Enum<E>> Class<E>.codec() = StreamCodec.Enum(this)
fun <E : Enum<E>> KClass<E>.codec() = StreamCodec.Enum(this.java)

View File

@ -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)
}
}

View File

@ -4,7 +4,9 @@ package ru.dbotthepony.kommons.util
import java.util.function.Consumer
import java.util.function.Supplier
import kotlin.reflect.KMutableProperty0
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
/**
* [Supplier] and [Consumer] combined in single interface
@ -48,7 +50,11 @@ interface Delegate<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 {

View File

@ -25,22 +25,21 @@ interface Listenable<V> {
*/
class Impl<V> : Listenable<V>, Consumer<V> {
private inner class L(val callback: Consumer<V>) : Listenable.L {
private var isRemoved = false
init {
subscribers.add(this)
}
override fun remove() {
if (!isRemoved) {
isRemoved = true
subscribers.remove(this)
}
subscribers.remove(this)
}
}
private val subscribers = CopyOnWriteArrayList<L>()
fun clear() {
subscribers.clear()
}
override fun addListener(listener: Consumer<V>): Listenable.L {
return L(listener)
}
@ -68,6 +67,10 @@ interface Listenable<V> {
private val subscribers = CopyOnWriteArrayList<L>()
fun clear() {
subscribers.clear()
}
override fun addListener(listener: Runnable): Listenable.L {
return L(listener)
}

View File

@ -4,6 +4,7 @@ import java.util.concurrent.locks.ReentrantLock
import java.util.function.Consumer
import java.util.function.Supplier
import kotlin.concurrent.withLock
import kotlin.reflect.KProperty0
interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
class UnboundBox<V> : ListenableDelegate<V> {
@ -20,6 +21,10 @@ interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
return get as V
}
fun clearListeners() {
listeners.clear()
}
override fun accept(t: V) {
value = t
}
@ -34,6 +39,10 @@ interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
return listeners.addListener(listener)
}
fun clearListeners() {
listeners.clear()
}
override fun get(): V {
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> {
private val value = Box(value)
fun clearListeners() {
value.clearListeners()
}
override fun addListener(listener: Consumer<V>): Listenable.L {
return value.addListener(listener)
}
@ -94,6 +107,10 @@ interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
return listeners.addListener(listener)
}
fun clearListeners() {
listeners.clear()
}
override fun observe(): Boolean {
if (shadow !== Companion)
return false
@ -142,6 +159,8 @@ interface ListenableDelegate<V> : Delegate<V>, Listenable<V> {
}
class Shadow<V>(parent: Supplier<V>) : AbstractShadow<V>(parent) {
constructor(parent: KProperty0<V>) : this(parent::get)
override fun compare(a: V, b: V): Boolean {
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 {
return comparator(a, b)
}