diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Identities.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Identities.kt index 1c7575662..a18978297 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Identities.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Identities.kt @@ -103,11 +103,11 @@ class Identities(override val dynamicKind : IdKind) : IIdentities { return RdIdUtil.mix(rdId, tail) } - override fun mix(rdId: RdId, tail: Int): RdId { + fun mix(rdId: RdId, tail: Int): RdId { return RdIdUtil.mix(rdId, tail) } - override fun mix(rdId: RdId, tail: Long): RdId { + fun mix(rdId: RdId, tail: Long): RdId { return RdIdUtil.mix(rdId, tail) } } @@ -141,14 +141,12 @@ class SequentialIdentities(override val dynamicKind : IdKind) : IIdentities { } override fun mix(rdId: RdId, tail: String): RdId { - return RdId(StableMask or RdIdUtil.mix(rdId, tail).hash) - } - - override fun mix(rdId: RdId, tail: Int): RdId { - return RdId(StableMask or RdIdUtil.mix(rdId, tail).hash) - } - - override fun mix(rdId: RdId, tail: Long): RdId { - return RdId(StableMask or RdIdUtil.mix(rdId, tail).hash) + // Since dynamic RdIds are generated sequentially, the parent RdId often uses only a small number of bits (low entropy). + // Additionally, the tail string may be short, which can increase the risk of hash collisions. + // To improve hash distribution and reliability, we mix in extra data (tail length and a constant string) before mixing the tail itself. + var newRdId = RdIdUtil.mix(rdId, tail.length) + newRdId = RdIdUtil.mix(newRdId, "SequentialIdentities::Mix::RdId") + newRdId = RdIdUtil.mix(newRdId, tail) + return RdId(StableMask or newRdId.hash) } } diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Interfaces.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Interfaces.kt index 337b96ae7..52769609d 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Interfaces.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Interfaces.kt @@ -173,16 +173,6 @@ interface IIdentities { * Creates a stable identifier by mixing the parent ID with a string key. */ fun mix(rdId: RdId, tail: String): RdId - - /** - * Creates a stable identifier by mixing the parent ID with an integer key. - */ - fun mix(rdId: RdId, tail: Int): RdId - - /** - * Creates a stable identifier by mixing the parent ID with a long key. - */ - fun mix(rdId: RdId, tail: Long): RdId } /** diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Protocol.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Protocol.kt index 9f1c73b2f..d9fb8dfad 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Protocol.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/Protocol.kt @@ -130,7 +130,7 @@ class Protocol internal constructor( val newExtension = create() extensions[clazz] = newExtension val declName = clazz.simpleName ?: error("Can't get simple name for class $clazz") - newExtension.identify(identity, identity.mix(RdId.Null, declName)) + newExtension.identify(identity, identity.mix(RdId.Null, declName), true) newExtension.bindTopLevel(lifetime, this, declName) newExtension } diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/IRdBindable.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/IRdBindable.kt index b38a56da4..c51f03d6d 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/IRdBindable.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/IRdBindable.kt @@ -5,6 +5,7 @@ package com.jetbrains.rd.framework.base import com.jetbrains.rd.framework.IIdentities import com.jetbrains.rd.framework.IProtocol import com.jetbrains.rd.framework.IRdDynamic +import com.jetbrains.rd.framework.Identities import com.jetbrains.rd.framework.RdId import com.jetbrains.rd.util.lifetime.Lifetime @@ -26,9 +27,25 @@ interface IRdBindable : IRdDynamic { fun bind() /** - * Assigns IDs to this node and its child nodes in the graph. + * Assigns an [RdId] to this node and recursively to all its child nodes. + * + * @param identities the identity source used to generate child IDs. + * @param id the [RdId] to assign to this node. + * @param stable a recommendation for how child RdIds should be generated. Entities may override this value. + * - `true` — uses [IIdentities.mix] to produce hash-based, deterministic IDs derived from the parent ID + * and child name. Used when the same entity exists on both protocol sides and its children need + * matching IDs to find each other (e.g., extensions with built-in maps, sets, properties). + * Given the same parent ID, both sides will compute identical child IDs. + * - `false` — uses [IIdentities.next] to produce dynamic IDs. + * Used for entities created at runtime (e.g., items added to RdMap/RdList) where each side assigns + * its own IDs independently. + * + * Entities can override this parameter when their children require a specific strategy. For example, + * [RdExtBase] forces `stable = true` regardless of the incoming value, because its built-in children + * are part of a statically known structure that must match on both protocol sides. So even if an ext + * is encountered during dynamic (non-stable) identification, it will switch to stable IDs for its own subtree. */ - fun identify(identities: IIdentities, id: RdId) + fun identify(identities: IIdentities, id: RdId, stable: Boolean) /** * Creates a clone of this IRdBindable not bound to any protocol @@ -36,25 +53,54 @@ interface IRdBindable : IRdDynamic { fun deepClone() : IRdBindable = TODO("This is a base implementation of deepClone. Shouldn't be invoked. Introduced for AWS plugin to compile with Rider SDK 19.2.") } +private fun computeChildRdId(identities: IIdentities, parent: RdId, stable: Boolean, i: Int): RdId { + return if (stable) { + if (identities is Identities) { + // for backward compatibility + identities.mix(parent, i) + } else { + identities.mix(parent, i.toString(2)) + } + } else { + identities.next(parent) + } +} + //generator comprehension methods fun T.preBind(lf: Lifetime, parent: IRdDynamic, name: String) = this?.preBind(lf, parent, name) fun T.bind() = this?.bind() -fun T.identify(identities: IIdentities, ids: RdId) = this?.identify(identities, ids) +fun T.identify(identities: IIdentities, ids: RdId, stable: Boolean) = this?.identify(identities, ids, stable) -fun Array.identify(identities: IIdentities, ids: RdId) = forEachIndexed { i, v -> v?.identify(identities, identities.mix(ids, i))} +fun Array.identify(identities: IIdentities, ids: RdId, stable: Boolean) = forEachIndexed { i, v -> v?.identify( + identities, + computeChildRdId(identities, ids, stable, i), + stable +)} fun Array.preBind(lf: Lifetime, parent: IRdDynamic, name: String) = forEachIndexed { i, v -> v?.preBind(lf,parent, "$name[$i]")} fun Array.bind() = forEachIndexed { i, v -> v?.bind()} -fun List.identify(identities: IIdentities, ids: RdId) = forEachIndexed { i, v -> v?.identify(identities, identities.mix(ids, i))} +fun List.identify(identities: IIdentities, ids: RdId, stable: Boolean) = forEachIndexed { i, v -> v?.identify( + identities, + computeChildRdId(identities, ids, stable, i), + stable, +)} fun List.preBind(lf: Lifetime, parent: IRdDynamic, name: String) = forEachIndexed { i, v -> v?.preBind(lf,parent, "$name[$i]")} fun List.bind() = forEachIndexed { i, v -> v?.bind()} -internal fun Any?.identifyPolymorphic(identities: IIdentities, ids: RdId) { +internal fun Any?.identifyPolymorphic(identities: IIdentities, ids: RdId, stable: Boolean) { if (this is IRdBindable) { - this.identify(identities, ids) + this.identify(identities, ids, stable) } else { - (this as? Array<*>)?.forEachIndexed { i, v -> (v as? IRdBindable)?.identify(identities, identities.mix(ids, i))} - (this as? List<*>)?.forEachIndexed { i, v -> (v as? IRdBindable)?.identify(identities, identities.mix(ids, i))} + (this as? Array<*>)?.forEachIndexed { i, v -> (v as? IRdBindable)?.identify( + identities, + computeChildRdId(identities, ids, stable, i), + stable, + )} + (this as? List<*>)?.forEachIndexed { i, v -> (v as? IRdBindable)?.identify( + identities, + computeChildRdId(identities, ids, stable, i), + stable, + )} } } diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/RdBindableBase.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/RdBindableBase.kt index 1fe32b1ed..3cd33f7d2 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/RdBindableBase.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/RdBindableBase.kt @@ -138,7 +138,7 @@ abstract class RdBindableBase : IRdBindable, IPrintable { val localBindLifetime = bindLifetime if (localBindLifetime.isAlive) { if (newExtension.rdid == RdId.Null) - newExtension.identify(proto.identity, proto.identity.mix(rdid, ".$name")) + newExtension.identify(proto.identity, proto.identity.mix(rdid, ".$name"), true) newExtension.preBind(localBindLifetime, this, name) newExtension.bind() } @@ -205,13 +205,21 @@ abstract class RdBindableBase : IRdBindable, IPrintable { return child.findByRName(rName.dropNonEmptyRoot()) } - override fun identify(identities: IIdentities, id: RdId) { + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { require(rdid.isNull) { "Already has RdId: $rdid, entity: $this" } require(!id.isNull) { "Assigned RdId mustn't be null, entity: $this" } rdid = id for ((name, child) in bindableChildren) { - child?.identifyPolymorphic(identities, identities.mix(id, ".$name")) + child?.identifyPolymorphic(identities, computeChildRdId(identities, id, name, stable), stable) + } + } + + private fun computeChildRdId(identities: IIdentities, parent: RdId, name: String, stable: Boolean): RdId { + return if (stable) { + identities.mix(parent, ".$name") + } else { + identities.next(parent) } } diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/RdExtBase.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/RdExtBase.kt index 2121ff58e..fdd90a3a7 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/RdExtBase.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/base/RdExtBase.kt @@ -113,6 +113,14 @@ abstract class RdExtBase : RdReactiveBase() { }) } + override fun identify( + identities: IIdentities, + id: RdId, + stable: Boolean + ) { + super.identify(identities, id, true) // enforce true to make stable ids for each built-in child + } + override fun assertBindingThread() = Unit override fun onWireReceived(proto: IProtocol, buffer: AbstractBuffer, ctx: SerializationCtx, dispatchHelper: IRdWireableDispatchHelper) { diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdMap.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdMap.kt index ccc6ed4ac..d4da97636 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdMap.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdMap.kt @@ -77,9 +77,9 @@ class AsyncRdMap private constructor( } } - override fun identify(identities: IIdentities, id: RdId) { + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { synchronized(map) { - map.identify(identities, id) + map.identify(identities, id, stable) } } diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdProperty.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdProperty.kt index d182905ae..685c78d2a 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdProperty.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdProperty.kt @@ -162,7 +162,7 @@ class AsyncRdProperty(val valueSerializer: ISerializer = Polymorphic()) : } } - override fun identify(identities: IIdentities, id: RdId) { + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { require(!id.isNull) { "Assigned RdId mustn't be null, entity: $this" } synchronized (property) { diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdSet.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdSet.kt index 61825d86a..a24e9c73d 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdSet.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/AsyncRdSet.kt @@ -76,9 +76,9 @@ class AsyncRdSet private constructor( } } - override fun identify(identities: IIdentities, id: RdId) { + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { synchronized(set) { - set.identify(identities, id) + set.identify(identities, id, stable) } } diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/InternRoot.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/InternRoot.kt index 05607092e..da64582e7 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/InternRoot.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/InternRoot.kt @@ -130,7 +130,7 @@ class InternRoot(val serializer: ISerializer = Polymorphic()) } - override fun identify(identities: IIdentities, id: RdId) { + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { require(rdid.isNull) { "Already has RdId: $rdid, entity: $this" } require(!id.isNull) { "Assigned RdId mustn't be null, entity: $this" } diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdList.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdList.kt index 105111b6c..931d0ca7f 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdList.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdList.kt @@ -65,7 +65,7 @@ class RdList private constructor(val valSzr: ISerializer, private va for (index in 0 until size) { val item = this[index] if (item != null) { - item.identifyPolymorphic(proto.identity, proto.identity.next(rdid)) + item.identifyPolymorphic(proto.identity, proto.identity.next(rdid), false) definitions.add(tryPreBindValue(lifetime, item, index, false)) } } @@ -95,7 +95,7 @@ class RdList private constructor(val valSzr: ISerializer, private va val value = it.newValueOpt if (it !is IViewableList.Event.Remove) { - value.identifyPolymorphic(proto.identity, proto.identity.next(rdid)) + value.identifyPolymorphic(proto.identity, proto.identity.next(rdid), false) definitions.add(it.index, tryPreBindValue(lifetime, value, it.index, false)) } } diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdMap.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdMap.kt index d78d8245e..f8c3befe1 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdMap.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdMap.kt @@ -64,7 +64,7 @@ open class RdMap private constructor( for ((key, value) in this) { if (value != null) { - value.identifyPolymorphic(proto.identity, proto.identity.next(rdid)) + value.identifyPolymorphic(proto.identity, proto.identity.next(rdid), false) val definition = tryPreBindValue(lifetime, key, value, false) if (definition != null) definitions[key] = definition @@ -96,7 +96,7 @@ open class RdMap private constructor( if (it !is IViewableMap.Event.Remove) { val value = it.newValueOpt - value.identifyPolymorphic(proto.identity, proto.identity.next(rdid)) + value.identifyPolymorphic(proto.identity, proto.identity.next(rdid), false) val definition = tryPreBindValue(lifetime, it.key, value, false) definitions.put(it.key, definition)?.terminate() } diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdProperty.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdProperty.kt index 4cf6cde68..a66043bc0 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdProperty.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdProperty.kt @@ -80,7 +80,7 @@ abstract class RdPropertyBase(val valueSerializer: ISerializer) : RdReacti // We need to terminate the current lifetime to unbind the existing value before assigning a new value, especially in cases where we are reassigning it. bindDefinition.get()?.terminate() - v.identifyPolymorphic(proto.identity, proto.identity.next(rdid)) + v.identifyPolymorphic(proto.identity, proto.identity.next(rdid), false) val prevDefinition = bindDefinition.getAndSet(tryPreBindValue(lifetime, v, false)) prevDefinition?.terminate() @@ -207,10 +207,10 @@ class RdOptionalProperty(valueSerializer: ISerializer = Polymorphic( } } - override fun identify(identities: IIdentities, id: RdId) { - super.identify(identities, id) + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { + super.identify(identities, id, stable) if (!optimizeNested) - valueOrNull?.identifyPolymorphic(identities, identities.next(id)) + valueOrNull?.identifyPolymorphic(identities, identities.next(id), false) } override fun advise(lifetime: Lifetime, handler: (T) -> Unit) { @@ -290,10 +290,10 @@ class RdProperty(defaultValue: T, valueSerializer: ISerializer = Polymorph value = newValue } - override fun identify(identities: IIdentities, id: RdId) { - super.identify(identities, id) + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { + super.identify(identities, id, stable) if (!optimizeNested) - value?.identifyPolymorphic(identities, identities.next(id)) + value?.identifyPolymorphic(identities, identities.next(id), false) } override fun advise(lifetime: Lifetime, handler: (T) -> Unit) { diff --git a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdTask.kt b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdTask.kt index 3b9da081a..8dbe2e97d 100644 --- a/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdTask.kt +++ b/rd-kt/rd-framework/src/main/kotlin/com/jetbrains/rd/framework/impl/RdTask.kt @@ -161,7 +161,7 @@ class EndpointWiredRdTask( return@adviseOnce } - taskResult.value.identifyPolymorphic(proto.identity, proto.identity.next(rdid)) + taskResult.value.identifyPolymorphic(proto.identity, proto.identity.next(rdid), false) lifetime.executeIfAlive { taskResult.value.preBindPolymorphic(lifetime, call, rdid.toString()) if (lifetime.isNotAlive) diff --git a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdIdTest.kt b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdIdTest.kt index 3f48869ce..21973f91a 100644 --- a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdIdTest.kt +++ b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdIdTest.kt @@ -41,8 +41,6 @@ class RdIdTest { val serverIdentities = SequentialIdentities(IdKind.Server) val testStrings = listOf("", "a", "test", "Protocol", "Extension", "InternRoot") - val testInts = listOf(0, 1, -1, Int.MAX_VALUE, Int.MIN_VALUE) - val testLongs = listOf(0L, 1L, -1L, Long.MAX_VALUE, Long.MIN_VALUE) for (s in testStrings) { val clientId = clientIdentities.mix(RdId.Null, s) @@ -50,20 +48,6 @@ class RdIdTest { assertTrue(clientId.hash and HIGH_BIT != 0L, "Client stable ID from string '$s' should have high bit set") assertTrue(serverId.hash and HIGH_BIT != 0L, "Server stable ID from string '$s' should have high bit set") } - - for (i in testInts) { - val clientId = clientIdentities.mix(RdId.Null, i) - val serverId = serverIdentities.mix(RdId.Null, i) - assertTrue(clientId.hash and HIGH_BIT != 0L, "Client stable ID from int $i should have high bit set") - assertTrue(serverId.hash and HIGH_BIT != 0L, "Server stable ID from int $i should have high bit set") - } - - for (l in testLongs) { - val clientId = clientIdentities.mix(RdId.Null, l) - val serverId = serverIdentities.mix(RdId.Null, l) - assertTrue(clientId.hash and HIGH_BIT != 0L, "Client stable ID from long $l should have high bit set") - assertTrue(serverId.hash and HIGH_BIT != 0L, "Server stable ID from long $l should have high bit set") - } } @Test diff --git a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdSignalTest.kt b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdSignalTest.kt index 647ba9a8b..d3a317877 100644 --- a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdSignalTest.kt +++ b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/RdSignalTest.kt @@ -131,8 +131,8 @@ class RdSignalTest : RdFrameworkTestBase() { _foo.bind() } - override fun identify(identities: IIdentities, id: RdId) { - _foo.identify(identities, identities.mix(id, "foo")) + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { + _foo.identify(identities, identities.mix(id, "foo"), stable) } @@ -279,8 +279,8 @@ class RdSignalTest : RdFrameworkTestBase() { _foo.bind() } - override fun identify(identities: IIdentities, id: RdId) { - _foo.identify(identities, identities.mix(id, "foo")) + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { + _foo.identify(identities, identities.mix(id, "foo"), stable) } constructor() : this(RdSignal()) diff --git a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/contexts/RdPerContextMapTest.kt b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/contexts/RdPerContextMapTest.kt index 222f50627..fe74d2123 100644 --- a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/contexts/RdPerContextMapTest.kt +++ b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/contexts/RdPerContextMapTest.kt @@ -246,7 +246,7 @@ class RdPerContextMapTest : RdFrameworkTestBase() { serverMap.bindTopLevel(serverLifetime, serverProtocol, "map") key.updateValue(server1Cid).use { - serverProtocol.wire.send(serverProtocol.identity.mix(RdId.Null, 10)) {} // trigger key addition by protocol write + serverProtocol.wire.send(serverProtocol.identity.mix(RdId.Null, "10")) {} // trigger key addition by protocol write } assertTrue(serverProtocol.contexts.getValueSet(key).contains(server1Cid)) diff --git a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/InterningRemovalsTest.kt b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/InterningRemovalsTest.kt index 93e2862e0..899f06e7e 100644 --- a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/InterningRemovalsTest.kt +++ b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/InterningRemovalsTest.kt @@ -71,7 +71,7 @@ class InterningRemovalsTest : RdFrameworkTestBase() { } private fun InternRoot.bindStatic(protocol: IProtocol, id: String): InternRoot { - identify(protocol.identity, protocol.identity.mix(RdId.Null, id)) + identify(protocol.identity, protocol.identity.mix(RdId.Null, id), true) bindTopLevel(if (protocol === clientProtocol) clientLifetime else serverLifetime, protocol, id) return this } diff --git a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/InterningTest.kt b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/InterningTest.kt index 75285f014..192e9b73b 100644 --- a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/InterningTest.kt +++ b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/InterningTest.kt @@ -289,8 +289,8 @@ class InterningTest: RdFrameworkTestBase() { val serverProperty = RdOptionalProperty(InterningTestModel).slave() val clientProperty = RdOptionalProperty(InterningTestModel) - serverProperty.identify(serverProtocol.identity, RdId(1L)) - clientProperty.identify(clientProtocol.identity, RdId(1L)) + serverProperty.identify(serverProtocol.identity, RdId(1L), true) + clientProperty.identify(clientProtocol.identity, RdId(1L), true) serverProtocol.bindStatic(serverProperty, "top") clientProtocol.bindStatic(clientProperty, "top") @@ -326,7 +326,7 @@ class InterningTest: RdFrameworkTestBase() { private fun InternRoot.bindStatic(protocol: IProtocol, id: String) : InternRoot { - identify(protocol.identity, protocol.identity.mix(RdId.Null, id)) + identify(protocol.identity, protocol.identity.mix(RdId.Null, id), true) bindTopLevel(if(protocol === clientProtocol) clientLifetime else serverLifetime, protocol, id) return this } diff --git a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/PropertyHolderWithInternRoot.kt b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/PropertyHolderWithInternRoot.kt index eabd9a3cf..9f831228b 100644 --- a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/PropertyHolderWithInternRoot.kt +++ b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/cases/interning/PropertyHolderWithInternRoot.kt @@ -25,9 +25,9 @@ class PropertyHolderWithInternRoot(val property: RdOptionalProperty, super.bind() } - override fun identify(identities: IIdentities, id: RdId) { - property.identify(identities, identities.mix(id, "propertyHolderWithInternRoot")) - super.identify(identities, id) + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { + property.identify(identities, identities.mix(id, "propertyHolderWithInternRoot"), stable) + super.identify(identities, id, stable) } override val serializationContext: SerializationCtx diff --git a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/util/DynamicEntity.kt b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/util/DynamicEntity.kt index 3e6f6cc7a..41461f40c 100644 --- a/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/util/DynamicEntity.kt +++ b/rd-kt/rd-framework/src/test/kotlin/com/jetbrains/rd/framework/test/util/DynamicEntity.kt @@ -41,8 +41,8 @@ class DynamicEntity(val _foo: RdProperty) : RdBindableBase() { _foo.bind() } - override fun identify(identities: IIdentities, id: RdId) { - _foo.identify(identities, identities.mix(id, "foo")) + override fun identify(identities: IIdentities, id: RdId, stable: Boolean) { + _foo.identify(identities, identities.mix(id, "foo"), stable) } constructor(_foo: T) : this(RdProperty(_foo, Polymorphic())) diff --git a/rd-kt/rd-gen/src/main/kotlin/com/jetbrains/rd/generator/nova/csharp/CSharp50Generator.kt b/rd-kt/rd-gen/src/main/kotlin/com/jetbrains/rd/generator/nova/csharp/CSharp50Generator.kt index 1947c250c..9039a18c1 100644 --- a/rd-kt/rd-gen/src/main/kotlin/com/jetbrains/rd/generator/nova/csharp/CSharp50Generator.kt +++ b/rd-kt/rd-gen/src/main/kotlin/com/jetbrains/rd/generator/nova/csharp/CSharp50Generator.kt @@ -692,7 +692,7 @@ open class CSharp50Generator( +"{" indent { - +"Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, \"${decl.name}\"));" + +"Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, \"${decl.name}\"), true);" +"this.BindTopLevel(lifetime, protocol, \"${decl.name}\");" //better than nameof(${decl.name}) because one could rename generated class and it'll still able to connect to Kt } +"}" diff --git a/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/asis/AsyncPrimitivesExt.cs b/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/asis/AsyncPrimitivesExt.cs index ff4a21f7f..bd2d23a79 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/asis/AsyncPrimitivesExt.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/asis/AsyncPrimitivesExt.cs @@ -106,7 +106,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public AsyncPrimitivesExt(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "AsyncPrimitivesExt")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "AsyncPrimitivesExt"), true); this.BindTopLevel(lifetime, protocol, "AsyncPrimitivesExt"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/asis/AsyncPrimitivesRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/asis/AsyncPrimitivesRoot.cs index ff47cf917..c4adfb1b8 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/asis/AsyncPrimitivesRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/asis/AsyncPrimitivesRoot.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public AsyncPrimitivesRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "AsyncPrimitivesRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "AsyncPrimitivesRoot"), true); this.BindTopLevel(lifetime, protocol, "AsyncPrimitivesRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/reversed/AsyncPrimitivesExt.cs b/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/reversed/AsyncPrimitivesExt.cs index ff4a21f7f..bd2d23a79 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/reversed/AsyncPrimitivesExt.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/reversed/AsyncPrimitivesExt.cs @@ -106,7 +106,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public AsyncPrimitivesExt(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "AsyncPrimitivesExt")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "AsyncPrimitivesExt"), true); this.BindTopLevel(lifetime, protocol, "AsyncPrimitivesExt"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/reversed/AsyncPrimitivesRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/reversed/AsyncPrimitivesRoot.cs index ff47cf917..c4adfb1b8 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/reversed/AsyncPrimitivesRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/asyncPrimitives/reversed/AsyncPrimitivesRoot.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public AsyncPrimitivesRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "AsyncPrimitivesRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "AsyncPrimitivesRoot"), true); this.BindTopLevel(lifetime, protocol, "AsyncPrimitivesRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/documentationModelTest/asis/DocumentationModelRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/documentationModelTest/asis/DocumentationModelRoot.cs index 441a92e4f..0eaa83cfe 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/documentationModelTest/asis/DocumentationModelRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/documentationModelTest/asis/DocumentationModelRoot.cs @@ -68,7 +68,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public DocumentationModelRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "DocumentationModelRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "DocumentationModelRoot"), true); this.BindTopLevel(lifetime, protocol, "DocumentationModelRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/documentationModelTest/reversed/DocumentationModelRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/documentationModelTest/reversed/DocumentationModelRoot.cs index 441a92e4f..0eaa83cfe 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/documentationModelTest/reversed/DocumentationModelRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/documentationModelTest/reversed/DocumentationModelRoot.cs @@ -68,7 +68,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public DocumentationModelRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "DocumentationModelRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "DocumentationModelRoot"), true); this.BindTopLevel(lifetime, protocol, "DocumentationModelRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/asis/ExampleModelNova.cs b/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/asis/ExampleModelNova.cs index 80ae9ea15..a1c2659f1 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/asis/ExampleModelNova.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/asis/ExampleModelNova.cs @@ -141,7 +141,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public ExampleModelNova(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExampleModelNova")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExampleModelNova"), true); this.BindTopLevel(lifetime, protocol, "ExampleModelNova"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/asis/ExampleRootNova.cs b/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/asis/ExampleRootNova.cs index bc03794d3..66f4a2c3f 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/asis/ExampleRootNova.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/asis/ExampleRootNova.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public ExampleRootNova(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExampleRootNova")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExampleRootNova"), true); this.BindTopLevel(lifetime, protocol, "ExampleRootNova"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/reversed/ExampleModelNova.cs b/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/reversed/ExampleModelNova.cs index 1abf1d9bd..26dfb3ba1 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/reversed/ExampleModelNova.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/reversed/ExampleModelNova.cs @@ -141,7 +141,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public ExampleModelNova(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExampleModelNova")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExampleModelNova"), true); this.BindTopLevel(lifetime, protocol, "ExampleModelNova"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/reversed/ExampleRootNova.cs b/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/reversed/ExampleRootNova.cs index bc03794d3..66f4a2c3f 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/reversed/ExampleRootNova.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/exampleModelTest/reversed/ExampleRootNova.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public ExampleRootNova(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExampleRootNova")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExampleRootNova"), true); this.BindTopLevel(lifetime, protocol, "ExampleRootNova"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/extensionTest/asis/ExtensionRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/extensionTest/asis/ExtensionRoot.cs index 5aec4a85c..6fce237c6 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/extensionTest/asis/ExtensionRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/extensionTest/asis/ExtensionRoot.cs @@ -66,7 +66,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public ExtensionRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExtensionRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExtensionRoot"), true); this.BindTopLevel(lifetime, protocol, "ExtensionRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/extensionTest/reversed/ExtensionRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/extensionTest/reversed/ExtensionRoot.cs index ef225df38..ffc5933c3 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/extensionTest/reversed/ExtensionRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/extensionTest/reversed/ExtensionRoot.cs @@ -66,7 +66,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public ExtensionRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExtensionRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "ExtensionRoot"), true); this.BindTopLevel(lifetime, protocol, "ExtensionRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/asis/Solution2.cs b/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/asis/Solution2.cs index 083724be6..97aa6c322 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/asis/Solution2.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/asis/Solution2.cs @@ -141,7 +141,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public Solution2(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "Solution2")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "Solution2"), true); this.BindTopLevel(lifetime, protocol, "Solution2"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/asis/TestRoot1.cs b/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/asis/TestRoot1.cs index f592e938d..d654a1367 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/asis/TestRoot1.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/asis/TestRoot1.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public TestRoot1(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "TestRoot1")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "TestRoot1"), true); this.BindTopLevel(lifetime, protocol, "TestRoot1"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/reversed/Solution2.cs b/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/reversed/Solution2.cs index cea1b9557..540d29dd2 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/reversed/Solution2.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/reversed/Solution2.cs @@ -141,7 +141,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public Solution2(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "Solution2")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "Solution2"), true); this.BindTopLevel(lifetime, protocol, "Solution2"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/reversed/TestRoot1.cs b/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/reversed/TestRoot1.cs index f592e938d..d654a1367 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/reversed/TestRoot1.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/factoryFqn/reversed/TestRoot1.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public TestRoot1(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "TestRoot1")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "TestRoot1"), true); this.BindTopLevel(lifetime, protocol, "TestRoot1"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/DefaultFieldValuesRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/DefaultFieldValuesRoot.cs index 7ccedff49..615e438d0 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/DefaultFieldValuesRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/DefaultFieldValuesRoot.cs @@ -66,7 +66,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public DefaultFieldValuesRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "DefaultFieldValuesRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "DefaultFieldValuesRoot"), true); this.BindTopLevel(lifetime, protocol, "DefaultFieldValuesRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/InheritsAutomationExtension.cs b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/InheritsAutomationExtension.cs index 5c0cd3505..f0733ea14 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/InheritsAutomationExtension.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/InheritsAutomationExtension.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public InheritsAutomationExtension(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "InheritsAutomationExtension")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "InheritsAutomationExtension"), true); this.BindTopLevel(lifetime, protocol, "InheritsAutomationExtension"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/InheritsAutomationRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/InheritsAutomationRoot.cs index 2c3d84437..5d970d410 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/InheritsAutomationRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/asis/InheritsAutomationRoot.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public InheritsAutomationRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "InheritsAutomationRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "InheritsAutomationRoot"), true); this.BindTopLevel(lifetime, protocol, "InheritsAutomationRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/DefaultFieldValuesRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/DefaultFieldValuesRoot.cs index 7ccedff49..615e438d0 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/DefaultFieldValuesRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/DefaultFieldValuesRoot.cs @@ -66,7 +66,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public DefaultFieldValuesRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "DefaultFieldValuesRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "DefaultFieldValuesRoot"), true); this.BindTopLevel(lifetime, protocol, "DefaultFieldValuesRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/InheritsAutomationExtension.cs b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/InheritsAutomationExtension.cs index 5c0cd3505..f0733ea14 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/InheritsAutomationExtension.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/InheritsAutomationExtension.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public InheritsAutomationExtension(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "InheritsAutomationExtension")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "InheritsAutomationExtension"), true); this.BindTopLevel(lifetime, protocol, "InheritsAutomationExtension"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/InheritsAutomationRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/InheritsAutomationRoot.cs index 2c3d84437..5d970d410 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/InheritsAutomationRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/inheritsAutomation/reversed/InheritsAutomationRoot.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public InheritsAutomationRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "InheritsAutomationRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "InheritsAutomationRoot"), true); this.BindTopLevel(lifetime, protocol, "InheritsAutomationRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/perClientId/asis/PerClientIdRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/perClientId/asis/PerClientIdRoot.cs index 53c3bf3d5..5ec045f6d 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/perClientId/asis/PerClientIdRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/perClientId/asis/PerClientIdRoot.cs @@ -112,7 +112,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public PerClientIdRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "PerClientIdRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "PerClientIdRoot"), true); this.BindTopLevel(lifetime, protocol, "PerClientIdRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/perClientId/reversed/PerClientIdRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/perClientId/reversed/PerClientIdRoot.cs index 53c3bf3d5..5ec045f6d 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/perClientId/reversed/PerClientIdRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/perClientId/reversed/PerClientIdRoot.cs @@ -112,7 +112,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public PerClientIdRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "PerClientIdRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "PerClientIdRoot"), true); this.BindTopLevel(lifetime, protocol, "PerClientIdRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/asis/RecursivePolymorphicModel.cs b/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/asis/RecursivePolymorphicModel.cs index 6dcb06610..7c6b67a65 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/asis/RecursivePolymorphicModel.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/asis/RecursivePolymorphicModel.cs @@ -91,7 +91,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public RecursivePolymorphicModel(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RecursivePolymorphicModel")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RecursivePolymorphicModel"), true); this.BindTopLevel(lifetime, protocol, "RecursivePolymorphicModel"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/asis/RecursivePolymorphicModelRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/asis/RecursivePolymorphicModelRoot.cs index b7d4b624d..06f273edd 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/asis/RecursivePolymorphicModelRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/asis/RecursivePolymorphicModelRoot.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public RecursivePolymorphicModelRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RecursivePolymorphicModelRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RecursivePolymorphicModelRoot"), true); this.BindTopLevel(lifetime, protocol, "RecursivePolymorphicModelRoot"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/reversed/RecursivePolymorphicModel.cs b/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/reversed/RecursivePolymorphicModel.cs index 6dcb06610..7c6b67a65 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/reversed/RecursivePolymorphicModel.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/reversed/RecursivePolymorphicModel.cs @@ -91,7 +91,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public RecursivePolymorphicModel(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RecursivePolymorphicModel")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RecursivePolymorphicModel"), true); this.BindTopLevel(lifetime, protocol, "RecursivePolymorphicModel"); } diff --git a/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/reversed/RecursivePolymorphicModelRoot.cs b/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/reversed/RecursivePolymorphicModelRoot.cs index b7d4b624d..06f273edd 100644 --- a/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/reversed/RecursivePolymorphicModelRoot.cs +++ b/rd-kt/rd-gen/src/test/resources/testData/recursivePolymorphicModelTest/reversed/RecursivePolymorphicModelRoot.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public RecursivePolymorphicModelRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RecursivePolymorphicModelRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RecursivePolymorphicModelRoot"), true); this.BindTopLevel(lifetime, protocol, "RecursivePolymorphicModelRoot"); } diff --git a/rd-kt/rd-text/src/test/kotlin/com/jetbrains/rd/rdtext/test/cases/TextBufferTest.kt b/rd-kt/rd-text/src/test/kotlin/com/jetbrains/rd/rdtext/test/cases/TextBufferTest.kt index af70721b4..c119aece0 100644 --- a/rd-kt/rd-text/src/test/kotlin/com/jetbrains/rd/rdtext/test/cases/TextBufferTest.kt +++ b/rd-kt/rd-text/src/test/kotlin/com/jetbrains/rd/rdtext/test/cases/TextBufferTest.kt @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test private fun IRdBindable.top(lifetime: Lifetime, protocol: IProtocol) { - identify(protocol.identity, protocol.identity.mix(RdId.Null, this.javaClass.simpleName)) + identify(protocol.identity, protocol.identity.mix(RdId.Null, this.javaClass.simpleName), true) preBind(lifetime, protocol, this.javaClass.simpleName) bind() } diff --git a/rd-net/RdFramework.Reflection/RdExtReflectionBindableBase.cs b/rd-net/RdFramework.Reflection/RdExtReflectionBindableBase.cs index 4b156290c..9907197bf 100644 --- a/rd-net/RdFramework.Reflection/RdExtReflectionBindableBase.cs +++ b/rd-net/RdFramework.Reflection/RdExtReflectionBindableBase.cs @@ -40,10 +40,10 @@ protected override void InitBindableFields(Lifetime lifetime) base.InitBindableFields(lifetime); } - public override void Identify(IIdentities identities, RdId id) + public override void Identify(IIdentities identities, RdId id, bool stable) { ((IReflectionBindable) this).EnsureBindableChildren(); - base.Identify(identities, id); + base.Identify(identities, id, true); // enforce true to make stable ids for each built-in child } public override string ToString() diff --git a/rd-net/RdFramework.Reflection/RdReflectionBindableBase.cs b/rd-net/RdFramework.Reflection/RdReflectionBindableBase.cs index e89727c07..0438c3e05 100644 --- a/rd-net/RdFramework.Reflection/RdReflectionBindableBase.cs +++ b/rd-net/RdFramework.Reflection/RdReflectionBindableBase.cs @@ -45,10 +45,10 @@ protected override void PreInitBindableFields(Lifetime lifetime) base.PreInitBindableFields(lifetime); } - public override void Identify(IIdentities identities, RdId id) + public override void Identify(IIdentities identities, RdId id, bool stable) { ((IReflectionBindable) this).EnsureBindableChildren(); - base.Identify(identities, id); + base.Identify(identities, id, stable); } public override string ToString() diff --git a/rd-net/RdFramework.Reflection/ReflectionRdActivator.cs b/rd-net/RdFramework.Reflection/ReflectionRdActivator.cs index 3b6fd8f53..6c5afb37d 100644 --- a/rd-net/RdFramework.Reflection/ReflectionRdActivator.cs +++ b/rd-net/RdFramework.Reflection/ReflectionRdActivator.cs @@ -68,7 +68,7 @@ public T ActivateBind(Lifetime lifetime, IProtocol protocol) where T : RdBind var instance = Activate(); var typename = GetTypeName(typeof(T)); - instance.Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, typename)); + instance.Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, typename), true); instance.BindTopLevel(lifetime, protocol, typename); return instance; @@ -85,7 +85,7 @@ public RdExtReflectionBindableBase ActivateBind(Type type, Lifetime lifetime, IP var typename = GetTypeName(type); var bindable = (RdExtReflectionBindableBase) instance; - bindable.Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, typename)); + bindable.Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, typename), true); bindable.BindTopLevel(lifetime, protocol, typename); return bindable; diff --git a/rd-net/RdFramework.Reflection/ReflectionSerializersFacade.cs b/rd-net/RdFramework.Reflection/ReflectionSerializersFacade.cs index 3f3f03b9a..fb483e154 100644 --- a/rd-net/RdFramework.Reflection/ReflectionSerializersFacade.cs +++ b/rd-net/RdFramework.Reflection/ReflectionSerializersFacade.cs @@ -52,7 +52,7 @@ public T InitBind(T instance, Lifetime lifetime, IProtocol protocol) private static void Bind(IRdBindable instance, Lifetime lifetime, IProtocol protocol) { var typename = ReflectionRdActivator.GetTypeName(instance.GetType()); - instance.Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, typename)); + instance.Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, typename), true); instance.BindTopLevel(lifetime, protocol, typename); } } diff --git a/rd-net/RdFramework/Base/IRdBindable.cs b/rd-net/RdFramework/Base/IRdBindable.cs index 26c256a66..f76861e62 100644 --- a/rd-net/RdFramework/Base/IRdBindable.cs +++ b/rd-net/RdFramework/Base/IRdBindable.cs @@ -67,7 +67,29 @@ public interface IRdBindable : IRdDynamic, IPrintable RdId RdId { get; set; } void PreBind(Lifetime lf, IRdDynamic parent, string name); void Bind(); - void Identify(IIdentities identities, RdId id); + /// + /// Assigns an to this node and recursively to all its child nodes. + /// + /// The identity source used to generate child IDs. + /// The to assign to this node. + /// + /// A recommendation for how child RdIds should be generated. Entities may override this value. + /// + /// true — uses to produce hash-based, deterministic IDs + /// derived from the parent ID and child name. Used when the same entity exists on both protocol sides + /// and its children need matching IDs to find each other (e.g., extensions with built-in maps, sets, + /// properties). Given the same parent ID, both sides will compute identical child IDs. + /// false — uses to produce dynamic IDs. + /// Used for entities created at runtime (e.g., items added to RdMap/RdList) where each side assigns + /// its own IDs independently. + /// + /// Entities can override this parameter when their children require a specific strategy. For example, + /// forces stable = true regardless of the incoming value, because its + /// built-in children are part of a statically known structure that must match on both protocol sides. + /// So even if an ext is encountered during dynamic (non-stable) identification, it will switch to stable + /// IDs for its own subtree. + /// + void Identify(IIdentities identities, RdId id, bool stable); } internal readonly ref struct AllowBindCookie @@ -228,42 +250,64 @@ public static void BindTopLevel(this T? value, Lifetime lifetime, IProtocol p #region Identify - internal static void IdentifyPolymorphic(this object? value, IIdentities ids, RdId id) + internal static void IdentifyPolymorphic(this object? value, IIdentities ids, RdId id, bool stable) { if (value is IRdBindable rdBindable) - rdBindable.Identify(ids, id); + rdBindable.Identify(ids, id, stable); else - (value as IEnumerable).Identify0(ids, id); + (value as IEnumerable).Identify0(ids, id, stable); } - private static void Identify0(this IEnumerable? items, IIdentities ids, RdId id) + private static void Identify0(this IEnumerable? items, IIdentities ids, RdId id, bool stable) { if (items == null) return; var i = 0; foreach (var x in items) { - (x as IRdBindable).IdentifyEx(ids, ids.Mix(id, i++)); + if (x is IRdBindable bindableChild) + { + var childId = ComputeChildRdId(ids, id, stable, i++); + bindableChild.IdentifyEx(ids, childId, stable); + } } } + + private static RdId ComputeChildRdId(IIdentities identities, RdId id, bool stable, int i) + { + if (stable) + { +#pragma warning disable CS0618 // Type or member is obsolete + if (identities is Identities legacyIdentities) +#pragma warning restore CS0618 // Type or member is obsolete + { + // for backward compatibility + return legacyIdentities.Mix(id, i++); + } + + return identities.Mix(id, Convert.ToString(i, 2)); + } + + return identities.Next(id); + } - public static void IdentifyEx(this T? value, IIdentities ids, RdId id) where T : IRdBindable + public static void IdentifyEx(this T? value, IIdentities ids, RdId id, bool stable) where T : IRdBindable { - if (value != null) value.Identify(ids, id); + if (value != null) value.Identify(ids, id, stable); } //PLEASE DON'T MERGE these two methods into one with IEnumerable, just believe me - public static void IdentifyEx(this List? items, IIdentities ids, RdId id) where T : IRdBindable + public static void IdentifyEx(this List? items, IIdentities ids, RdId id, bool stable) where T : IRdBindable { - items.Identify0(ids, id); + items.Identify0(ids, id, stable); } - public static void IdentifyEx(this T[]? items, IIdentities ids, RdId id) where T : IRdBindable + public static void IdentifyEx(this T[]? items, IIdentities ids, RdId id, bool stable) where T : IRdBindable { - items.Identify0(ids, id); + items.Identify0(ids, id, stable); } #endregion diff --git a/rd-net/RdFramework/Base/RdBindableBase.cs b/rd-net/RdFramework/Base/RdBindableBase.cs index 98c6e1c56..c9168f387 100644 --- a/rd-net/RdFramework/Base/RdBindableBase.cs +++ b/rd-net/RdFramework/Base/RdBindableBase.cs @@ -179,7 +179,7 @@ protected virtual void InitBindableFields(Lifetime lifetime) } } - public virtual void Identify(IIdentities identities, RdId id) + public virtual void Identify(IIdentities identities, RdId id, bool stable) { Assertion.Require(RdId.IsNil, "Already has RdId: {0}, entity: {1}", RdId, this); Assertion.Require(!id.IsNil, "Assigned RdId mustn't be null, entity: {0}", this); @@ -187,10 +187,15 @@ public virtual void Identify(IIdentities identities, RdId id) RdId = id; foreach (var pair in BindableChildren) { - pair.Value?.IdentifyPolymorphic(identities, identities.Mix(id, "." + pair.Key)); + pair.Value?.IdentifyPolymorphic(identities, ComputeChildRdId(identities, id, pair.Key, stable), stable); } } + private static RdId ComputeChildRdId(IIdentities identities, RdId parent, string name, bool stable) + { + return stable ? identities.Mix(parent, "." + name) : identities.Next(parent); + } + public virtual RdBindableBase? FindByRName(RName rName) { var rootName = rName.GetNonEmptyRoot(); @@ -284,7 +289,7 @@ private T GetOrCreateExtension(string name, bool highPriorityExtension, Func< if (bindLifetime.IsAlive) { if (bindable.RdId == RdId.Nil) - bindable.Identify(proto.Identities, proto.Identities.Mix(RdId, "." + name)); + bindable.Identify(proto.Identities, proto.Identities.Mix(RdId, "." + name), true); bindable.PreBind(bindLifetime, this, name); bindable.Bind(); } diff --git a/rd-net/RdFramework/Base/RdDelegateBase.cs b/rd-net/RdFramework/Base/RdDelegateBase.cs index 2bc132c7a..73501d90c 100644 --- a/rd-net/RdFramework/Base/RdDelegateBase.cs +++ b/rd-net/RdFramework/Base/RdDelegateBase.cs @@ -31,7 +31,7 @@ public void Print(PrettyPrinter printer) public virtual void PreBind(Lifetime lf, IRdDynamic parent, string name) => Delegate.PreBind(lf, parent, name); public virtual void Bind() => Delegate.Bind(); - public void Identify(IIdentities identities, RdId id) => Delegate.Identify(identities, id); + public void Identify(IIdentities identities, RdId id, bool stable) => Delegate.Identify(identities, id, stable); public RdId RdId { diff --git a/rd-net/RdFramework/Base/RdExtBase.cs b/rd-net/RdFramework/Base/RdExtBase.cs index 155dd413b..c6580483e 100644 --- a/rd-net/RdFramework/Base/RdExtBase.cs +++ b/rd-net/RdFramework/Base/RdExtBase.cs @@ -51,7 +51,7 @@ protected override void Init(Lifetime lifetime, IProtocol parentProto, Serializa var parentWire = parentProto.Wire; parentProto.Serializers.RegisterToplevelOnce(GetType(), Register); -if (!TryGetSerializationContext(out var serializationContext)) + if (!TryGetSerializationContext(out var serializationContext)) return; var extScheduler = parentProto.Scheduler; @@ -60,7 +60,8 @@ protected override void Init(Lifetime lifetime, IProtocol parentProto, Serializa () => { var parentProtocolImpl = (Protocol)parentProto; - var proto = new Protocol(parentProto.Name, parentProto.Serializers, parentProto.Identities, extScheduler, myExtWire, lifetime, parentProtocolImpl, this.CreateExtSignal(parentProto.Identities)); + var proto = new Protocol(parentProto.Name, parentProto.Serializers, parentProto.Identities, extScheduler, + myExtWire, lifetime, parentProtocolImpl, this.CreateExtSignal(parentProto.Identities)); myExtProtocol = proto; //protocol must be set first to allow bindable bind to it @@ -75,14 +76,15 @@ protected override void Init(Lifetime lifetime, IProtocol parentProto, Serializa using (Signal.NonPriorityAdviseCookie.Create()) - { - - parentProtocolImpl.SubmitExtCreated(info); + { + parentProtocolImpl.SubmitExtCreated(info); } - parentWire.Advise(lifetime, this);SendState(parentWire, ExtState.Ready); + parentWire.Advise(lifetime, this); + SendState(parentWire, ExtState.Ready); - Protocol.InitTrace?.Log($"{this} :: bound");}, + Protocol.InitTrace?.Log($"{this} :: bound"); + }, () => { myExtProtocol = null; @@ -91,6 +93,11 @@ protected override void Init(Lifetime lifetime, IProtocol parentProto, Serializa ); } + public override void Identify(IIdentities identities, RdId id, bool stable) + { + base.Identify(identities, id, true); // enforce true to make stable ids for each built-in child + } + protected override void AssertBindingThread() { } diff --git a/rd-net/RdFramework/IIdentities.cs b/rd-net/RdFramework/IIdentities.cs index c46df1852..bbbc18a60 100644 --- a/rd-net/RdFramework/IIdentities.cs +++ b/rd-net/RdFramework/IIdentities.cs @@ -20,15 +20,5 @@ public interface IIdentities /// Creates a stable identifier by mixing the parent ID with a string key. /// RdId Mix(RdId rdId, string tail); - - /// - /// Creates a stable identifier by mixing the parent ID with an integer key. - /// - RdId Mix(RdId rdId, int tail); - - /// - /// Creates a stable identifier by mixing the parent ID with a long key. - /// - RdId Mix(RdId rdId, long tail); } } \ No newline at end of file diff --git a/rd-net/RdFramework/Impl/AsyncProperty.cs b/rd-net/RdFramework/Impl/AsyncProperty.cs index baf1bb066..059b608ae 100644 --- a/rd-net/RdFramework/Impl/AsyncProperty.cs +++ b/rd-net/RdFramework/Impl/AsyncProperty.cs @@ -161,7 +161,7 @@ public bool TryGetSerializationContext(out SerializationCtx ctx) public IAsyncSource Change => myChange; - public void Identify(IIdentities identities, RdId id) + public void Identify(IIdentities identities, RdId id, bool stable) { Assertion.Require(!id.IsNil, $"Assigned RdId mustn't be null, entity: {this}"); diff --git a/rd-net/RdFramework/Impl/AsyncRdMap.cs b/rd-net/RdFramework/Impl/AsyncRdMap.cs index f52143345..0fab6d628 100644 --- a/rd-net/RdFramework/Impl/AsyncRdMap.cs +++ b/rd-net/RdFramework/Impl/AsyncRdMap.cs @@ -52,10 +52,10 @@ public void Bind() myMap.Bind(); } - public void Identify(IIdentities identities, RdId id) + public void Identify(IIdentities identities, RdId id, bool stable) { lock (myMap) - myMap.Identify(identities, id); + myMap.Identify(identities, id, stable); } public bool OptimizeNested diff --git a/rd-net/RdFramework/Impl/AsyncRdSet.cs b/rd-net/RdFramework/Impl/AsyncRdSet.cs index be06ca6e5..43fb38cae 100644 --- a/rd-net/RdFramework/Impl/AsyncRdSet.cs +++ b/rd-net/RdFramework/Impl/AsyncRdSet.cs @@ -53,10 +53,10 @@ public void Bind() mySet.Bind(); } - public void Identify(IIdentities identities, RdId id) + public void Identify(IIdentities identities, RdId id, bool stable) { lock (mySet) - mySet.Identify(identities, id); + mySet.Identify(identities, id, stable); } public bool OptimizeNested diff --git a/rd-net/RdFramework/Impl/Identities.cs b/rd-net/RdFramework/Impl/Identities.cs index b1498dbbb..2c047047e 100644 --- a/rd-net/RdFramework/Impl/Identities.cs +++ b/rd-net/RdFramework/Impl/Identities.cs @@ -72,20 +72,16 @@ public RdId Next(RdId parent) // Ignore parent to avoid collisions from different creation order on client/server return new RdId(Interlocked.Add(ref myId, 2)); } - - public RdId Mix(RdId rdId, string tail) - { - return new RdId(StableMask | RdIdUtil.Mix(rdId, tail).Value); - } - - public RdId Mix(RdId rdId, int tail) - { - return new RdId(StableMask | RdIdUtil.Mix(rdId, tail).Value); - } - public RdId Mix(RdId rdId, long tail) + public RdId Mix(RdId rdId, string tail) { - return new RdId(StableMask | RdIdUtil.Mix(rdId, tail).Value); + // Since dynamic RdIds are generated sequentially, the parent RdId often uses only a small number of bits (low entropy). + // Additionally, the tail string may be short, which can increase the risk of hash collisions. + // To improve hash distribution and reliability, we mix in extra data (tail length and a constant string) before mixing the tail itself. + var newRdId = RdIdUtil.Mix(rdId, tail.Length); + newRdId = RdIdUtil.Mix(newRdId, "SequentialIdentities::Mix::RdId"); + newRdId = RdIdUtil.Mix(newRdId, tail); + return new RdId(StableMask | newRdId.Value); } } } \ No newline at end of file diff --git a/rd-net/RdFramework/Impl/InternRoot.cs b/rd-net/RdFramework/Impl/InternRoot.cs index 37f28a1d3..b7b967667 100644 --- a/rd-net/RdFramework/Impl/InternRoot.cs +++ b/rd-net/RdFramework/Impl/InternRoot.cs @@ -177,7 +177,7 @@ public void Bind() { } - public void Identify(IIdentities identities, RdId id) + public void Identify(IIdentities identities, RdId id, bool stable) { Assertion.Require(RdId.IsNil, "Already has RdId: {0}, entity: {1}", RdId, this); Assertion.Require(!id.IsNil, "Assigned RdId mustn't be null, entity: {0}", this); diff --git a/rd-net/RdFramework/Impl/Protocol.cs b/rd-net/RdFramework/Impl/Protocol.cs index cb1304170..cd97d03e8 100644 --- a/rd-net/RdFramework/Impl/Protocol.cs +++ b/rd-net/RdFramework/Impl/Protocol.cs @@ -172,7 +172,7 @@ public virtual T GetOrCreateExtension(Func create) where T : RdExtBase myExtensions[name] = res; if (res is IRdBindable rdBindable) { - rdBindable.Identify(Identities, Identities.Mix(RdId.Root, name)); + rdBindable.Identify(Identities, Identities.Mix(RdId.Root, name), true); rdBindable.PreBind(Lifetime, this, name); rdBindable.Bind(); } diff --git a/rd-net/RdFramework/Impl/RdList.cs b/rd-net/RdFramework/Impl/RdList.cs index 4073c9051..93bf1b799 100644 --- a/rd-net/RdFramework/Impl/RdList.cs +++ b/rd-net/RdFramework/Impl/RdList.cs @@ -112,7 +112,7 @@ protected override void PreInit(Lifetime lifetime, IProtocol proto) var item = this[index]; if (item != null) { - item.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId)); + item.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId), false); definitions.Add(TryPreBindValue(lifetime, item, index, false)); } } @@ -152,7 +152,7 @@ protected override void Init(Lifetime lifetime, IProtocol proto, SerializationCt if (it.Kind != AddUpdateRemove.Remove && it.NewValue != null) { - it.NewValue.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId)); + it.NewValue.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId), false); definitions.Insert(it.Index, TryPreBindValue(lifetime, it.NewValue, it.Index, false)); } } diff --git a/rd-net/RdFramework/Impl/RdMap.cs b/rd-net/RdFramework/Impl/RdMap.cs index b6a0e6efb..0b4d79baa 100644 --- a/rd-net/RdFramework/Impl/RdMap.cs +++ b/rd-net/RdFramework/Impl/RdMap.cs @@ -94,7 +94,7 @@ protected override void PreInit(Lifetime lifetime, IProtocol proto) { if (value != null) { - value.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId)); + value.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId), false); var definition = TryPreBindValue(lifetime, key, value, false); if (definition != null) definitions.Add(key, definition); @@ -140,7 +140,7 @@ protected override void Init(Lifetime lifetime, IProtocol proto, SerializationCt if (it.Kind != AddUpdateRemove.Remove) { - it.NewValue.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId)); + it.NewValue.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(RdId), false); var definition = TryPreBindValue(lifetime, it.Key, it.NewValue, false); definitions[it.Key] = definition; } diff --git a/rd-net/RdFramework/Impl/RdProperty.cs b/rd-net/RdFramework/Impl/RdProperty.cs index 9de4ccb70..ae61e5514 100644 --- a/rd-net/RdFramework/Impl/RdProperty.cs +++ b/rd-net/RdFramework/Impl/RdProperty.cs @@ -88,12 +88,12 @@ public static void Write(SerializationCtx ctx, UnsafeWriter writer, RdProperty StartInternal(Lifetime requestLifetime, TReq request, ISch if (result.Result.IsBindable()) { // we mock the endpoint side, since we are on stub wire, so identify bindable result here - result.Result.IdentifyPolymorphic(proto.Identities, proto.Identities.Mix(RdId, taskId.ToString())); + result.Result.IdentifyPolymorphic(proto.Identities, proto.Identities.Next(taskId), false); } task.OnResultReceived(result, new SynchronousDispatchHelper(taskId, requestLifetime)); diff --git a/rd-net/RdFramework/Tasks/WiredRdTask.cs b/rd-net/RdFramework/Tasks/WiredRdTask.cs index f2e4c2990..06736451f 100644 --- a/rd-net/RdFramework/Tasks/WiredRdTask.cs +++ b/rd-net/RdFramework/Tasks/WiredRdTask.cs @@ -171,7 +171,7 @@ public Endpoint(Lifetime bindLifetime, RdCall call, RdId rdId, ISche } var bindableRdId = proto.Identities.Next(RdId); - potentiallyBindable.IdentifyPolymorphic(proto.Identities, bindableRdId); + potentiallyBindable.IdentifyPolymorphic(proto.Identities, bindableRdId, false); using var cookie = Lifetime.UsingExecuteIfAlive(); if (cookie.Succeed) // lifetime can be terminated from background thread diff --git a/rd-net/Test.RdFramework/Contexts/RdPerContextMapTest.cs b/rd-net/Test.RdFramework/Contexts/RdPerContextMapTest.cs index 04a161669..e52de3b0b 100644 --- a/rd-net/Test.RdFramework/Contexts/RdPerContextMapTest.cs +++ b/rd-net/Test.RdFramework/Contexts/RdPerContextMapTest.cs @@ -230,7 +230,7 @@ public void TestLateBind06() using (key.UpdateValue(server1Cid)) { - ServerProtocol.Wire.Send(ServerProtocol.Identities.Mix(RdId.Nil, 10), _ => { }); + ServerProtocol.Wire.Send(ServerProtocol.Identities.Mix(RdId.Nil, "10"), _ => { }); } Assert.True(ServerProtocol.Contexts.GetValueSet(key).Contains(server1Cid)); diff --git a/rd-net/Test.RdFramework/Interning/InterningTestModel.cs b/rd-net/Test.RdFramework/Interning/InterningTestModel.cs index f52f1acc2..85ab5ab76 100644 --- a/rd-net/Test.RdFramework/Interning/InterningTestModel.cs +++ b/rd-net/Test.RdFramework/Interning/InterningTestModel.cs @@ -53,7 +53,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public InterningRoot1(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, GetType().Name)); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, GetType().Name), true); this.BindTopLevel(lifetime, protocol, GetType().Name); Protocol.InitTrace?.Log ($"CREATED toplevel object {this.PrintToString()}"); } diff --git a/rd-net/Test.RdFramework/Interning/InterningTestPropertyWrapper.cs b/rd-net/Test.RdFramework/Interning/InterningTestPropertyWrapper.cs index 632a9e016..85efad1df 100644 --- a/rd-net/Test.RdFramework/Interning/InterningTestPropertyWrapper.cs +++ b/rd-net/Test.RdFramework/Interning/InterningTestPropertyWrapper.cs @@ -30,10 +30,10 @@ protected override void Init(Lifetime lifetime, IProtocol proto, SerializationCt base.Init(lifetime, proto, ctx); } - public override void Identify(IIdentities identities, RdId id) + public override void Identify(IIdentities identities, RdId id, bool stable) { - Property.Identify(identities, id); - base.Identify(identities, id); + Property.Identify(identities, id, stable); + base.Identify(identities, id, stable); } } } \ No newline at end of file diff --git a/rd-net/Test.RdFramework/RdIdHierarchyGuardTest.cs b/rd-net/Test.RdFramework/RdIdHierarchyGuardTest.cs index 3af7357e4..5406dc185 100644 --- a/rd-net/Test.RdFramework/RdIdHierarchyGuardTest.cs +++ b/rd-net/Test.RdFramework/RdIdHierarchyGuardTest.cs @@ -80,9 +80,9 @@ protected override void Init(Lifetime lifetime, IProtocol proto, SerializationCt myValue.Bind(); } - public override void Identify(IIdentities identities, RdId id) + public override void Identify(IIdentities identities, RdId id, bool stable) { - myValue.Identify(identities, id); + myValue.Identify(identities, id, stable); } } } diff --git a/rd-net/Test.RdFramework/Reflection/ProxyGeneratorCustomSignalTest.cs b/rd-net/Test.RdFramework/Reflection/ProxyGeneratorCustomSignalTest.cs index e357b699e..584a7c52f 100644 --- a/rd-net/Test.RdFramework/Reflection/ProxyGeneratorCustomSignalTest.cs +++ b/rd-net/Test.RdFramework/Reflection/ProxyGeneratorCustomSignalTest.cs @@ -97,9 +97,9 @@ public void Bind() public bool TryGetSerializationContext(out SerializationCtx ctx) => myRdSignal.TryGetSerializationContext(out ctx); - public void Identify(IIdentities identities, RdId id) + public void Identify(IIdentities identities, RdId id, bool stable) { - myRdSignal.Identify(identities, id); + myRdSignal.Identify(identities, id, stable); } } } diff --git a/rd-net/Test.RdFramework/Reflection/data/Generated/RefExt.cs b/rd-net/Test.RdFramework/Reflection/data/Generated/RefExt.cs index 7b6fb1c08..d7542b632 100644 --- a/rd-net/Test.RdFramework/Reflection/data/Generated/RefExt.cs +++ b/rd-net/Test.RdFramework/Reflection/data/Generated/RefExt.cs @@ -96,7 +96,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public RefExt(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RefExt")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RefExt"), true); this.BindTopLevel(lifetime, protocol, "RefExt"); } diff --git a/rd-net/Test.RdFramework/Reflection/data/Generated/RefRoot.cs b/rd-net/Test.RdFramework/Reflection/data/Generated/RefRoot.cs index 686c758b1..209bdcde2 100644 --- a/rd-net/Test.RdFramework/Reflection/data/Generated/RefRoot.cs +++ b/rd-net/Test.RdFramework/Reflection/data/Generated/RefRoot.cs @@ -67,7 +67,7 @@ public static void RegisterDeclaredTypesSerializers(ISerializers serializers) public RefRoot(Lifetime lifetime, IProtocol protocol) : this() { - Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RefRoot")); + Identify(protocol.Identities, protocol.Identities.Mix(RdId.Root, "RefRoot"), true); this.BindTopLevel(lifetime, protocol, "RefRoot"); } diff --git a/rd-net/Test.RdFramework/SequentialIdentitiesTest.cs b/rd-net/Test.RdFramework/SequentialIdentitiesTest.cs index e44d1bb20..160a44c1b 100644 --- a/rd-net/Test.RdFramework/SequentialIdentitiesTest.cs +++ b/rd-net/Test.RdFramework/SequentialIdentitiesTest.cs @@ -32,9 +32,7 @@ public void TestMixAlwaysHasHighBit() var serverIdentities = new SequentialIdentities(IdKind.Server); var testStrings = new[] { "", "a", "test", "Protocol", "Extension", "InternRoot" }; - var testInts = new[] { 0, 1, -1, int.MaxValue, int.MinValue }; - var testLongs = new[] { 0L, 1L, -1L, long.MaxValue, long.MinValue }; - + foreach (var s in testStrings) { var clientId = clientIdentities.Mix(RdId.Nil, s); @@ -42,22 +40,6 @@ public void TestMixAlwaysHasHighBit() Assert.That(clientId.Value & HighBit, Is.Not.EqualTo(0L), $"Client stable ID from string '{s}' should have high bit set"); Assert.That(serverId.Value & HighBit, Is.Not.EqualTo(0L), $"Server stable ID from string '{s}' should have high bit set"); } - - foreach (var i in testInts) - { - var clientId = clientIdentities.Mix(RdId.Nil, i); - var serverId = serverIdentities.Mix(RdId.Nil, i); - Assert.That(clientId.Value & HighBit, Is.Not.EqualTo(0L), $"Client stable ID from int {i} should have high bit set"); - Assert.That(serverId.Value & HighBit, Is.Not.EqualTo(0L), $"Server stable ID from int {i} should have high bit set"); - } - - foreach (var l in testLongs) - { - var clientId = clientIdentities.Mix(RdId.Nil, l); - var serverId = serverIdentities.Mix(RdId.Nil, l); - Assert.That(clientId.Value & HighBit, Is.Not.EqualTo(0L), $"Client stable ID from long {l} should have high bit set"); - Assert.That(serverId.Value & HighBit, Is.Not.EqualTo(0L), $"Server stable ID from long {l} should have high bit set"); - } } [Test] diff --git a/rd-net/Test.RdFramework/Util/RdBindableExUtilTest.cs b/rd-net/Test.RdFramework/Util/RdBindableExUtilTest.cs index c59b5acd5..038214f7f 100644 --- a/rd-net/Test.RdFramework/Util/RdBindableExUtilTest.cs +++ b/rd-net/Test.RdFramework/Util/RdBindableExUtilTest.cs @@ -77,6 +77,6 @@ private class RdBindableTestClass : IRdBindable public void Bind() { throw new NotImplementedException(); } - public void Identify(IIdentities identities, RdId id) { throw new NotImplementedException(); } + public void Identify(IIdentities identities, RdId id, bool stable) { throw new NotImplementedException(); } } } \ No newline at end of file