fix #74 duplicate constructor amd state vars with Lynon serialization

This commit is contained in:
Sergey Chernov 2025-12-04 23:23:31 +01:00
parent 603023962e
commit 0c31ec63ee
4 changed files with 103 additions and 22 deletions

View File

@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych"
version = "1.0.5-SNAPSHOT"
version = "1.0.6-SNAPSHOT"
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below

View File

@ -210,8 +210,13 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// using objlist allow for some optimizations:
val params = meta.params.map { readField(scope, it.name).value }
println("serializing $objClass with params: $params")
encoder.encodeAnyList(scope, params)
serializeStateVars(scope, encoder)
val vars = serializingVars.values.map { it.value }
println("encoding vars: $vars")
if (vars.isNotEmpty<Obj>()) {
encoder.encodeAnyList(scope, vars)
}
}
override suspend fun toJson(scope: Scope): JsonElement {
@ -232,9 +237,9 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
return JsonObject(result)
}
val instanceVars: Map<String, ObjRecord> by lazy {
instanceScope.objects.filter { it.value.type.serializable }
}
// val instanceVars: Map<String, ObjRecord> by lazy {
// instanceScope.objects.filter { it.value.type.serializable }
// }
val serializingVars: Map<String, ObjRecord> by lazy {
instanceScope.objects.filter {
@ -243,18 +248,11 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
it.value.isMutable }
}
protected suspend fun serializeStateVars(scope: Scope, encoder: LynonEncoder) {
val vars = instanceVars.values.map { it.value }
if (vars.isNotEmpty()) {
encoder.encodeAnyList(scope, vars)
}
}
internal suspend fun deserializeStateVars(scope: Scope, decoder: LynonDecoder) {
val localVars = instanceVars.values.toList()
val localVars = serializingVars.values.toList()
if (localVars.isNotEmpty()) {
val vars = decoder.decodeAnyList(scope)
if (vars.size > instanceVars.size)
if (vars.size > serializingVars.size)
scope.raiseIllegalArgument("serialized vars has bigger size than instance vars")
for ((i, v) in vars.withIndex()) {
localVars[i].value = v

View File

@ -3815,20 +3815,43 @@ class ScriptTest {
assertEquals(JSTest1("bar", 1, true), x.decodeSerializable<JSTest1>())
}
// @Test
// fun testInstanceVars() = runTest {
// val x = eval("""
// class T(x,y)
// T(1, 2)
// """.trimIndent()) as ObjInstance
// println(x.serializingVars.map { "${it.key}=${it.value.value}"})
// }
@Test
fun testInstanceVars() = runTest {
var x = eval("""
// in this case, x and y are constructor parameters, not instance variables:
class T(x,y) {
// and z is val and therefore needn't serialization either:
val z = x + y
}
T(1, 2)
""".trimIndent()) as ObjInstance
println(x.serializingVars.map { "${it.key}=${it.value.value}"})
// so serializingVars is empty:
assertEquals(emptyMap(), x.serializingVars)
x = eval("""
class T(x,y) {
// variable z though should be serialized:
var z = x + y
}
val x = T(1, 2)
x.z = 100
assertEquals(100, x.z)
x
""".trimIndent()) as ObjInstance
// z is instance var, it must present
val z = x.serializingVars["z"] ?: x.serializingVars["T::z"]
// and be mutable:
assertTrue( z!!.isMutable )
println(x.serializingVars.map { "${it.key}=${it.value.value}"})
}
@Test
fun memberValCantBeAssigned() = runTest {
eval("""
class Point(foo,bar) {
val t = 42
var r = 142
}
val p = Point(1,2)
// val should not be assignable:
@ -3837,8 +3860,13 @@ class ScriptTest {
// val field must be readonly:
assertThrows { p.t = "bad" }
p.r = 123
// and the value should not be changed
assertEqual(42, p.t)
// but r should be changed:
assertEqual(123, p.r)
""")
}

View File

@ -723,6 +723,61 @@ class Wallet( id, ownerKey, balance=0, createdAt=Instant.now().truncateToSecond(
assertEquals(cp, cp2)
""")
}
@Test
fun testClassSerializationSizes() = runTest {
testScope().eval("""
class Point(x=0,y=0)
// 1 bit - nonnull
// 4 bits type record
// 8 bits size (5)
// 1 bit uncompressed
// 40 bits "Point"
// 54 total:
assertEquals( 54, Lynon.encode("Point").size )
assertEquals( 7, Lynon.encode("Point").toBuffer().size )
// 1 bit - nonnull
// 4 bits type record
assertEquals( 5, Lynon.encode(0).size )
class Empty()
// 1 bit non-null
// 4 bits type record
// 54 bits "Empty"
// 4 bits list size
// dont know where 1 bit for not cached
assertEquals( 64, Lynon.encode(Empty()).size )
assertEquals( Empty(), Lynon.decode(Lynon.encode(Empty())) )
// Here the situation is dofferent: we have 2 in zeroes plus int size, but cache shrinks it
assertEquals( 70, Lynon.encode(Point()).size )
// two 1's added 16 bit (each short int is 8 bits)
assertEquals( 86, Lynon.encode(Point(1,1)).size )
assertEquals( 86, Lynon.encode(Point(1,2)).size )
// Now let's make it more complex: we add 1 var to save:
class Poin2(x=0,y=0) {
val z = x + y
}
// val must not be serialized so no change here:
assertEquals( 86, Lynon.encode(Poin2(1,2)).size )
// lets check size of homogenous list of one small int
// 8 bits 3
// 4 bits type
// 8 bits list size
// 2 bits not cached and not null
// 4 bits element type
assertEquals( 27, Lynon.encode([3]).size)
class Poin3(x=0,y=0) {
var z = x + y
}
// var must be serialized, but caching could reduce size:
assert( Lynon.encode(Poin3(1,2)).size <= 110)
""".trimIndent())
}
}