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