From e808169e348dc103603b79bee7108899e68efd12 Mon Sep 17 00:00:00 2001 From: Dingyi189-eng <123935337+Dingyi189-eng@users.noreply.github.com> Date: Sun, 17 May 2026 17:38:24 -0700 Subject: [PATCH 1/2] Fix BinaryExporter deserialization for multiple UserData lists (Fixes #2609) --- .../java/com/jme3/export/binary/BinaryInputCapsule.java | 4 ++++ .../src/test/java/com/jme3/export/JmeExporterTest.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java index 989152d453..e0555442fd 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java @@ -959,6 +959,10 @@ protected long readLong(byte[] content) throws IOException { } bytes = ByteUtils.rightAlignBytes(bytes, 8); long value = ByteUtils.convertLongFromBytes(bytes); + if (value == BinaryOutputCapsule.NULL_OBJECT + || value == BinaryOutputCapsule.DEFAULT_OBJECT) { + index -= 4; + } return value; } diff --git a/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java b/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java index 96113bb6c9..e36490f950 100644 --- a/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java +++ b/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java @@ -140,7 +140,7 @@ public void testSaveWithNullParent(JmeExporter exporter) throws IOException { public void testExporterConsistency(JmeExporter currentExporter) { // final boolean testXML = true; - final boolean testLists = false; + final boolean testLists = true; final boolean testMaps = true; final boolean printXML = false; From 747834ce0a279d528012ae2dfaff805e31a6d824 Mon Sep 17 00:00:00 2001 From: Dingyi189-eng <123935337+Dingyi189-eng@users.noreply.github.com> Date: Sun, 17 May 2026 17:53:53 -0700 Subject: [PATCH 2/2] Fix UserData list field prefixes for binary/XML export (Fixes #2609) --- .../main/java/com/jme3/scene/UserData.java | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/UserData.java b/jme3-core/src/main/java/com/jme3/scene/UserData.java index 2fb04f44d3..0d717abe7c 100644 --- a/jme3-core/src/main/java/com/jme3/scene/UserData.java +++ b/jme3-core/src/main/java/com/jme3/scene/UserData.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; /** * UserData is used to contain user data objects @@ -74,9 +75,15 @@ public final class UserData implements Savable { private static final int TYPE_SHORT = 10; private static final int TYPE_BYTE = 11; + private static final AtomicLong uniqueIdCounter = new AtomicLong(0); + + private static final String LEGACY_PREFIX = "0"; + protected byte type; protected Object value; + private transient volatile String uniqueId; + public UserData() { } @@ -104,6 +111,17 @@ public String toString() { return value.toString(); } + /** + * Prefix for list/map/array fields in the export capsule. + * Legacy files use "0" / "1"; new files use "udN_0" / "udN_1" (no ':' for XML). + */ + private String listFieldPrefix(int index) { + if (LEGACY_PREFIX.equals(uniqueId)) { + return String.valueOf(index); + } + return uniqueId + "_" + index; + } + public static byte getObjectType(Object type) { if (type instanceof Integer) { return TYPE_INTEGER; @@ -138,7 +156,16 @@ public static byte getObjectType(Object type) { public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(type, "type", (byte) 0); - + + if (uniqueId == null) { + synchronized (this) { + if (uniqueId == null) { + uniqueId = "ud" + uniqueIdCounter.incrementAndGet(); + } + } + } + oc.write(uniqueId, "uniqueId", null); + switch (type) { case TYPE_INTEGER: int i = (Integer) value; @@ -165,15 +192,15 @@ public void write(JmeExporter ex) throws IOException { oc.write(sav, "savableVal", null); break; case TYPE_LIST: - this.writeList(oc, (List) value, "0"); + this.writeList(oc, (List) value, listFieldPrefix(0)); break; case TYPE_MAP: Map map = (Map) value; - this.writeList(oc, map.keySet(), "0"); - this.writeList(oc, map.values(), "1"); + this.writeList(oc, map.keySet(), listFieldPrefix(0)); + this.writeList(oc, map.values(), listFieldPrefix(1)); break; case TYPE_ARRAY: - this.writeList(oc, Arrays.asList((Object[]) value), "0"); + this.writeList(oc, Arrays.asList((Object[]) value), listFieldPrefix(0)); break; case TYPE_DOUBLE: Double d = (Double) value; @@ -196,6 +223,12 @@ public void write(JmeExporter ex) throws IOException { public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); type = ic.readByte("type", (byte) 0); + + uniqueId = ic.readString("uniqueId", null); + if (uniqueId == null) { + uniqueId = LEGACY_PREFIX; + } + switch (type) { case TYPE_INTEGER: value = ic.readInt("intVal", 0); @@ -216,19 +249,19 @@ public void read(JmeImporter im) throws IOException { value = ic.readSavable("savableVal", null); break; case TYPE_LIST: - value = this.readList(ic, "0"); + value = this.readList(ic, listFieldPrefix(0)); break; case TYPE_MAP: Map map = new HashMap<>(); - List keys = this.readList(ic, "0"); - List values = this.readList(ic, "1"); + List keys = this.readList(ic, listFieldPrefix(0)); + List values = this.readList(ic, listFieldPrefix(1)); for (int i = 0; i < keys.size(); ++i) { map.put(keys.get(i), values.get(i)); } value = map; break; case TYPE_ARRAY: - value = this.readList(ic, "0").toArray(); + value = this.readList(ic, listFieldPrefix(0)).toArray(); break; case TYPE_DOUBLE: value = ic.readDouble("doubleVal", 0.);