diff --git a/core/pom.xml b/core/pom.xml
index ef588ddfc..261add1a4 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -4,7 +4,7 @@
com.threerings
narya-parent
- 1.20
+ 1.21
narya
diff --git a/core/src/main/java/com/threerings/io/GenStreamUtil.java b/core/src/main/java/com/threerings/io/GenStreamUtil.java
new file mode 100644
index 000000000..ad3b3fb46
--- /dev/null
+++ b/core/src/main/java/com/threerings/io/GenStreamUtil.java
@@ -0,0 +1,107 @@
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2025 Three Rings Design, Inc., All Rights Reserved
+// https://github.com/threerings/narya/blob/master/LICENSE
+
+package com.threerings.io;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.ConcurrentHashMap;
+
+import java.io.IOException;
+
+/**
+ * Runtime support for the explicit {@code readObject}/{@code writeObject} methods emitted by {@link
+ * com.threerings.presents.tools.GenStreamableTask} in "flatten" mode.
+ *
+ *
The default reflective {@link Streamer} streams a class's fields in {@link
+ * com.samskivert.util.ClassUtil#getFields} order, which is superclass-first but relies on
+ * {@link Class#getDeclaredFields} within each class. That per-class order is not defined by
+ * the JVM spec and differs between HotSpot (server/desktop) and ART (Android) — so a default
+ * streamed object written by one and read by the other misaligns. The generated methods stream each
+ * field explicitly, in an order captured from HotSpot at build time, by calling {@link #readField}/
+ * {@link #writeField} here. Because those methods delegate to the very same {@link FieldMarshaller}
+ * the reflective streamer uses, the produced bytes are identical to the legacy reflective form on
+ * HotSpot (so existing persisted boards and DB blobs read unchanged, and desktop↔server is
+ * unaffected) while being deterministic on ART.
+ */
+public final class GenStreamUtil
+{
+ /**
+ * Reads the named field of {@code target} (declared by {@code declaringClass}) from the stream,
+ * using the same {@link FieldMarshaller} the reflective streamer would use.
+ */
+ public static void readField (
+ Class> declaringClass, String name, Object target, ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ Entry entry = entry(declaringClass, name);
+ try {
+ entry.marshaller.readField(entry.field, target, in);
+ } catch (IOException ioe) {
+ throw ioe;
+ } catch (ClassNotFoundException cnfe) {
+ throw cnfe;
+ } catch (RuntimeException re) {
+ throw re;
+ } catch (Exception e) {
+ throw new IOException("Failure reading field [class=" + declaringClass.getName() +
+ ", field=" + name + "]", e);
+ }
+ }
+
+ /**
+ * Writes the named field of {@code source} (declared by {@code declaringClass}) to the stream,
+ * using the same {@link FieldMarshaller} the reflective streamer would use.
+ */
+ public static void writeField (
+ Class> declaringClass, String name, Object source, ObjectOutputStream out)
+ throws IOException
+ {
+ Entry entry = entry(declaringClass, name);
+ try {
+ entry.marshaller.writeField(entry.field, source, out);
+ } catch (IOException ioe) {
+ throw ioe;
+ } catch (RuntimeException re) {
+ throw re;
+ } catch (Exception e) {
+ throw new IOException("Failure writing field [class=" + declaringClass.getName() +
+ ", field=" + name + "]", e);
+ }
+ }
+
+ protected static Entry entry (Class> declaringClass, String name)
+ {
+ String key = declaringClass.getName() + "#" + name;
+ Entry entry = _cache.get(key);
+ if (entry == null) {
+ Field field;
+ try {
+ field = declaringClass.getDeclaredField(name);
+ } catch (NoSuchFieldException nsfe) {
+ throw new RuntimeException("Missing streamed field [class=" +
+ declaringClass.getName() + ", field=" + name + "]", nsfe);
+ }
+ field.setAccessible(true);
+ entry = new Entry(field, FieldMarshaller.getFieldMarshaller(field));
+ _cache.put(key, entry);
+ }
+ return entry;
+ }
+
+ protected static final class Entry
+ {
+ public final Field field;
+ public final FieldMarshaller marshaller;
+ public Entry (Field field, FieldMarshaller marshaller) {
+ this.field = field;
+ this.marshaller = marshaller;
+ }
+ }
+
+ /** Caches the resolved (accessible) field + its marshaller, keyed by declaring class + name. */
+ protected static final ConcurrentHashMap _cache = new ConcurrentHashMap();
+
+ private GenStreamUtil () {}
+}
diff --git a/core/src/main/java/com/threerings/io/ObjectInputStream.java b/core/src/main/java/com/threerings/io/ObjectInputStream.java
index 6f02c8037..7847b96f4 100644
--- a/core/src/main/java/com/threerings/io/ObjectInputStream.java
+++ b/core/src/main/java/com/threerings/io/ObjectInputStream.java
@@ -412,5 +412,5 @@ public class ObjectInputStream extends DataInputStream
protected static volatile List _allowedPrefixes;
/** Used to activate verbose debug logging. */
- protected static final boolean STREAM_DEBUG = false;
+ protected static final boolean STREAM_DEBUG = Boolean.getBoolean("com.threerings.io.streamDebug");
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/AttributeChangedEvent.java b/core/src/main/java/com/threerings/presents/dobj/AttributeChangedEvent.java
index 55018da9f..5be7ce87b 100644
--- a/core/src/main/java/com/threerings/presents/dobj/AttributeChangedEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/AttributeChangedEvent.java
@@ -165,4 +165,24 @@ public class AttributeChangedEvent extends NamedEvent
protected Object _value;
protected transient Object _oldValue = UNSET_OLD_VALUE;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.AttributeChangedEvent.class, "_value", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.AttributeChangedEvent.class, "_value", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/CompoundEvent.java b/core/src/main/java/com/threerings/presents/dobj/CompoundEvent.java
index 68095279d..27c6ec633 100644
--- a/core/src/main/java/com/threerings/presents/dobj/CompoundEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/CompoundEvent.java
@@ -173,4 +173,22 @@ public class CompoundEvent extends DEvent
/** A list of the events associated with this compound event. */
protected StreamableArrayList _events;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.CompoundEvent.class, "_events", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.CompoundEvent.class, "_events", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/ElementUpdatedEvent.java b/core/src/main/java/com/threerings/presents/dobj/ElementUpdatedEvent.java
index 92eebc00b..0a83caf84 100644
--- a/core/src/main/java/com/threerings/presents/dobj/ElementUpdatedEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/ElementUpdatedEvent.java
@@ -178,4 +178,26 @@ public class ElementUpdatedEvent extends NamedEvent
protected Object _value;
protected int _index;
protected transient Object _oldValue = UNSET_OLD_VALUE;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.ElementUpdatedEvent.class, "_value", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.ElementUpdatedEvent.class, "_index", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.ElementUpdatedEvent.class, "_value", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.ElementUpdatedEvent.class, "_index", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/EntryAddedEvent.java b/core/src/main/java/com/threerings/presents/dobj/EntryAddedEvent.java
index 73a3e1488..34f355eca 100644
--- a/core/src/main/java/com/threerings/presents/dobj/EntryAddedEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/EntryAddedEvent.java
@@ -107,4 +107,24 @@ public class EntryAddedEvent extends EntryEvent
/** Used when this event is generated on the authoritative server where object changes are made
* immediately. This lets us know not to apply ourselves when we're actually dispatched. */
protected transient boolean _alreadyApplied;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.EntryAddedEvent.class, "_entry", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.EntryAddedEvent.class, "_entry", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/EntryRemovedEvent.java b/core/src/main/java/com/threerings/presents/dobj/EntryRemovedEvent.java
index 100add072..44752b5ea 100644
--- a/core/src/main/java/com/threerings/presents/dobj/EntryRemovedEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/EntryRemovedEvent.java
@@ -109,4 +109,24 @@ public class EntryRemovedEvent extends EntryEvent
@SuppressWarnings("unchecked")
protected transient T _oldEntry = (T)UNSET_OLD_ENTRY;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.EntryRemovedEvent.class, "_key", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.EntryRemovedEvent.class, "_key", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/EntryUpdatedEvent.java b/core/src/main/java/com/threerings/presents/dobj/EntryUpdatedEvent.java
index 9f7a11ddb..95025b552 100644
--- a/core/src/main/java/com/threerings/presents/dobj/EntryUpdatedEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/EntryUpdatedEvent.java
@@ -112,4 +112,24 @@ public class EntryUpdatedEvent extends EntryEvent
@SuppressWarnings("unchecked")
protected transient T _oldEntry = (T)UNSET_OLD_ENTRY;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.EntryUpdatedEvent.class, "_entry", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.EntryUpdatedEvent.class, "_entry", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/InvocationNotificationEvent.java b/core/src/main/java/com/threerings/presents/dobj/InvocationNotificationEvent.java
index 314d3a0f8..6e7f97f39 100644
--- a/core/src/main/java/com/threerings/presents/dobj/InvocationNotificationEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/InvocationNotificationEvent.java
@@ -89,4 +89,26 @@ public class InvocationNotificationEvent extends DEvent
/** The arguments to the receiver method being invoked. */
protected Object[] _args;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.InvocationNotificationEvent.class, "_receiverId", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.InvocationNotificationEvent.class, "_methodId", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.InvocationNotificationEvent.class, "_args", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.InvocationNotificationEvent.class, "_receiverId", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.InvocationNotificationEvent.class, "_methodId", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.InvocationNotificationEvent.class, "_args", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/InvocationRequestEvent.java b/core/src/main/java/com/threerings/presents/dobj/InvocationRequestEvent.java
index 88ff249ac..c2a74ace9 100644
--- a/core/src/main/java/com/threerings/presents/dobj/InvocationRequestEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/InvocationRequestEvent.java
@@ -89,4 +89,26 @@ public class InvocationRequestEvent extends DEvent
/** The arguments to the method being invoked. */
protected Object[] _args;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.InvocationRequestEvent.class, "_invCode", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.InvocationRequestEvent.class, "_methodId", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.InvocationRequestEvent.class, "_args", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.InvocationRequestEvent.class, "_invCode", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.InvocationRequestEvent.class, "_methodId", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.InvocationRequestEvent.class, "_args", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/InvocationResponseEvent.java b/core/src/main/java/com/threerings/presents/dobj/InvocationResponseEvent.java
index 43b38b836..aa7dd0c31 100644
--- a/core/src/main/java/com/threerings/presents/dobj/InvocationResponseEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/InvocationResponseEvent.java
@@ -88,4 +88,26 @@ public class InvocationResponseEvent extends DEvent
/** The arguments to the method being invoked. */
protected Object[] _args;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.InvocationResponseEvent.class, "_requestId", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.InvocationResponseEvent.class, "_methodId", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.InvocationResponseEvent.class, "_args", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.InvocationResponseEvent.class, "_requestId", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.InvocationResponseEvent.class, "_methodId", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.InvocationResponseEvent.class, "_args", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/MessageEvent.java b/core/src/main/java/com/threerings/presents/dobj/MessageEvent.java
index 219d6e764..b68342652 100644
--- a/core/src/main/java/com/threerings/presents/dobj/MessageEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/MessageEvent.java
@@ -74,4 +74,24 @@ public class MessageEvent extends NamedEvent
}
protected Object[] _args;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.MessageEvent.class, "_args", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.MessageEvent.class, "_args", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/ObjectAddedEvent.java b/core/src/main/java/com/threerings/presents/dobj/ObjectAddedEvent.java
index 4ef8a31f2..c13912087 100644
--- a/core/src/main/java/com/threerings/presents/dobj/ObjectAddedEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/ObjectAddedEvent.java
@@ -78,4 +78,24 @@ public class ObjectAddedEvent extends NamedEvent
protected int _oid;
protected transient boolean _alreadyApplied;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.ObjectAddedEvent.class, "_oid", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.ObjectAddedEvent.class, "_oid", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/ObjectDestroyedEvent.java b/core/src/main/java/com/threerings/presents/dobj/ObjectDestroyedEvent.java
index 947abb0d5..00148497f 100644
--- a/core/src/main/java/com/threerings/presents/dobj/ObjectDestroyedEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/ObjectDestroyedEvent.java
@@ -47,4 +47,20 @@ public class ObjectDestroyedEvent extends DEvent
buf.append("DESTROY:");
super.toString(buf);
}
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/ObjectRemovedEvent.java b/core/src/main/java/com/threerings/presents/dobj/ObjectRemovedEvent.java
index 62e294033..cc05d72f2 100644
--- a/core/src/main/java/com/threerings/presents/dobj/ObjectRemovedEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/ObjectRemovedEvent.java
@@ -78,4 +78,24 @@ public class ObjectRemovedEvent extends NamedEvent
protected int _oid;
protected transient boolean _alreadyApplied;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.ObjectRemovedEvent.class, "_oid", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.ObjectRemovedEvent.class, "_oid", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/ReleaseLockEvent.java b/core/src/main/java/com/threerings/presents/dobj/ReleaseLockEvent.java
index 3e557f859..dd76121ee 100644
--- a/core/src/main/java/com/threerings/presents/dobj/ReleaseLockEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/ReleaseLockEvent.java
@@ -55,4 +55,22 @@ public class ReleaseLockEvent extends NamedEvent
buf.append("UNLOCK:");
super.toString(buf);
}
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/dobj/ServerMessageEvent.java b/core/src/main/java/com/threerings/presents/dobj/ServerMessageEvent.java
index 2e9b6cc4a..956c99720 100644
--- a/core/src/main/java/com/threerings/presents/dobj/ServerMessageEvent.java
+++ b/core/src/main/java/com/threerings/presents/dobj/ServerMessageEvent.java
@@ -31,4 +31,24 @@ public class ServerMessageEvent extends MessageEvent
// this is what makes us server-only
return true;
}
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.DEvent.class, "_toid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.dobj.MessageEvent.class, "_args", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.DEvent.class, "_toid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.NamedEvent.class, "_name", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.dobj.MessageEvent.class, "_args", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/net/AESAuthRequest.java b/core/src/main/java/com/threerings/presents/net/AESAuthRequest.java
index aaae81d2f..82edc9fc7 100644
--- a/core/src/main/java/com/threerings/presents/net/AESAuthRequest.java
+++ b/core/src/main/java/com/threerings/presents/net/AESAuthRequest.java
@@ -113,7 +113,11 @@ public class AESAuthRequest extends AuthRequest
public void writeObject (ObjectOutputStream out)
throws IOException
{
- out.defaultWriteObject();
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.UpstreamMessage.class, "messageId", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.AuthRequest.class, "_creds", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.AuthRequest.class, "_version", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.AuthRequest.class, "_zone", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.AuthRequest.class, "_bootGroups", this, out);
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream oOut = new ObjectOutputStream(byteOut);
oOut.writeObject(_clearCreds);
@@ -136,7 +140,11 @@ public class AESAuthRequest extends AuthRequest
public void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
- in.defaultReadObject();
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.UpstreamMessage.class, "messageId", this, in);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.AuthRequest.class, "_creds", this, in);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.AuthRequest.class, "_version", this, in);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.AuthRequest.class, "_zone", this, in);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.AuthRequest.class, "_bootGroups", this, in);
_contents = new byte[in.readInt()];
in.read(_contents);
}
diff --git a/core/src/main/java/com/threerings/presents/net/AuthRequest.java b/core/src/main/java/com/threerings/presents/net/AuthRequest.java
index e7dac1cb1..3f3c63862 100644
--- a/core/src/main/java/com/threerings/presents/net/AuthRequest.java
+++ b/core/src/main/java/com/threerings/presents/net/AuthRequest.java
@@ -89,7 +89,11 @@ public class AuthRequest extends UpstreamMessage
throws IOException, ClassNotFoundException
{
try {
- in.defaultReadObject();
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.UpstreamMessage.class, "messageId", this, in);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.AuthRequest.class, "_creds", this, in);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.AuthRequest.class, "_version", this, in);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.AuthRequest.class, "_zone", this, in);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.AuthRequest.class, "_bootGroups", this, in);
} catch (IOException ioe) {
// if we fail here because the client is old, leave ourselves with a partially
// initialized set of credentials, which the server will generally cope with by telling
@@ -108,4 +112,17 @@ public class AuthRequest extends UpstreamMessage
/** The set of bootstrap service groups this client is interested in. */
protected String[] _bootGroups;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.UpstreamMessage.class, "messageId", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.AuthRequest.class, "_creds", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.AuthRequest.class, "_version", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.AuthRequest.class, "_zone", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.AuthRequest.class, "_bootGroups", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/net/FailureResponse.java b/core/src/main/java/com/threerings/presents/net/FailureResponse.java
index 4702bcc33..f357add24 100644
--- a/core/src/main/java/com/threerings/presents/net/FailureResponse.java
+++ b/core/src/main/java/com/threerings/presents/net/FailureResponse.java
@@ -45,4 +45,24 @@ public class FailureResponse extends DownstreamMessage
protected int _oid;
protected String _message;
+
+ // AUTO-GENERATED: METHODS START
+ // from interface Streamable
+ public void readObject (com.threerings.io.ObjectInputStream ins)
+ throws java.io.IOException, java.lang.ClassNotFoundException
+ {
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.DownstreamMessage.class, "messageId", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.FailureResponse.class, "_oid", this, ins);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.FailureResponse.class, "_message", this, ins);
+ }
+
+ // from interface Streamable
+ public void writeObject (com.threerings.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.DownstreamMessage.class, "messageId", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.FailureResponse.class, "_oid", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.FailureResponse.class, "_message", this, out);
+ }
+ // AUTO-GENERATED: METHODS END
}
diff --git a/core/src/main/java/com/threerings/presents/net/PingRequest.java b/core/src/main/java/com/threerings/presents/net/PingRequest.java
index 77b207bb4..3a4b78447 100644
--- a/core/src/main/java/com/threerings/presents/net/PingRequest.java
+++ b/core/src/main/java/com/threerings/presents/net/PingRequest.java
@@ -63,7 +63,7 @@ public class PingRequest extends UpstreamMessage
// network
_packStamp = System.currentTimeMillis();
- out.defaultWriteObject();
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.UpstreamMessage.class, "messageId", this, out);
}
/**
@@ -76,7 +76,7 @@ public class PingRequest extends UpstreamMessage
// the network
_unpackStamp = System.currentTimeMillis();
- in.defaultReadObject();
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.UpstreamMessage.class, "messageId", this, in);
}
@Override
diff --git a/core/src/main/java/com/threerings/presents/net/PongResponse.java b/core/src/main/java/com/threerings/presents/net/PongResponse.java
index cca1ac6d3..6ed7a45fa 100644
--- a/core/src/main/java/com/threerings/presents/net/PongResponse.java
+++ b/core/src/main/java/com/threerings/presents/net/PongResponse.java
@@ -83,7 +83,9 @@ public class PongResponse extends DownstreamMessage
_processDelay = (int)(_packStamp - _pingStamp);
}
- out.defaultWriteObject();
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.DownstreamMessage.class, "messageId", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.PongResponse.class, "_packStamp", this, out);
+ com.threerings.io.GenStreamUtil.writeField(com.threerings.presents.net.PongResponse.class, "_processDelay", this, out);
}
/**
@@ -96,7 +98,9 @@ public class PongResponse extends DownstreamMessage
// the network
_unpackStamp = System.currentTimeMillis();
- in.defaultReadObject();
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.DownstreamMessage.class, "messageId", this, in);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.PongResponse.class, "_packStamp", this, in);
+ com.threerings.io.GenStreamUtil.readField(com.threerings.presents.net.PongResponse.class, "_processDelay", this, in);
}
@Override
diff --git a/pom.xml b/pom.xml
index a81c4cb73..a027437b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.threerings
narya-parent
pom
- 1.20
+ 1.21
Narya Parent
Facilities for making networked multiplayer games.
diff --git a/tools/pom.xml b/tools/pom.xml
index cf8b22459..b4f1e55ad 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -4,7 +4,7 @@
com.threerings
narya-parent
- 1.20
+ 1.21
narya-tools
diff --git a/tools/src/main/java/com/threerings/presents/tools/GenStreamableTask.java b/tools/src/main/java/com/threerings/presents/tools/GenStreamableTask.java
index 84f765667..bd70c4a8b 100644
--- a/tools/src/main/java/com/threerings/presents/tools/GenStreamableTask.java
+++ b/tools/src/main/java/com/threerings/presents/tools/GenStreamableTask.java
@@ -6,16 +6,24 @@
package com.threerings.presents.tools;
import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;
import com.google.common.collect.Lists;
+import com.samskivert.util.ClassUtil;
+
+import com.threerings.io.NotStreamable;
import com.threerings.io.Streamable;
import com.threerings.presents.dobj.DObject;
@@ -36,6 +44,21 @@ public class GenStreamableTask extends GenTask
_filesets.add(set);
}
+ /**
+ * If set, generate self-contained declaration-ordered {@code read/writeObject} methods
+ * that stream the entire flattened field set (in {@link ClassUtil#getFields} order, captured on
+ * HotSpot at build time) via {@link com.threerings.io.GenStreamUtil}, rather than the legacy
+ * per-class {@code super.read/writeObject(...)} + local-fields form. In this mode {@link DObject}
+ * subclasses are processed too, and a pass-through {@code readObject}/{@code writeObject} (one
+ * that calls {@code defaultReadObject}/{@code defaultWriteObject}) has that call replaced in
+ * place with the explicit field streaming (preserving any surrounding logic). This is what makes
+ * Streamables deterministic on ART (Android) while staying byte-identical on HotSpot. See {@link
+ * com.threerings.io.GenStreamUtil}.
+ */
+ public void setFlatten (boolean flatten) {
+ _flatten = flatten;
+ }
+
@Override
public void execute ()
{
@@ -86,6 +109,11 @@ public class GenStreamableTask extends GenTask
protected void processClass (File source, Class> sclass)
throws IOException
{
+ if (_flatten) {
+ processFlatten(source, sclass);
+ return;
+ }
+
StreamableClassRequirements reqs = new StreamableClassRequirements(sclass);
// we must implement Streamable, not be a DObject and have some fields that need to be
// streamed
@@ -144,6 +172,164 @@ public class GenStreamableTask extends GenTask
writeFile(source.getAbsolutePath(), sfile.generate(null, methods.toString()));
}
+ /**
+ * "Flatten" mode: emit self-contained declaration-ordered {@code read/writeObject} that stream
+ * the whole {@link ClassUtil#getFields} field set via {@link com.threerings.io.GenStreamUtil}.
+ * Handles {@link DObject}s and replaces pass-through {@code default*Object()} calls in place.
+ */
+ protected void processFlatten (File source, Class> sclass)
+ throws IOException
+ {
+ // must be a concrete Streamable; never instrument abstract bases (their concrete subclasses
+ // stream the full flattened field set themselves, so an abstract base carrying a read/write
+ // method would "poison" any subclass that didn't get its own — silently dropping fields)
+ if (!Streamable.class.isAssignableFrom(sclass) || sclass.isInterface() ||
+ sclass.isEnum() || Modifier.isAbstract(sclass.getModifiers())) {
+ return;
+ }
+
+ // gather the streamed fields in the exact order the reflective streamer uses (superclass
+ // first, then this class's declared fields), skipping @NotStreamable and closure refs
+ List fields = Lists.newArrayList();
+ for (Field field : ClassUtil.getFields(sclass)) {
+ if (field.getAnnotation(NotStreamable.class) != null) {
+ continue;
+ }
+ if (field.isSynthetic() && field.getName().startsWith("this$")) {
+ continue;
+ }
+ fields.add(field);
+ }
+ if (fields.isEmpty()) {
+ return;
+ }
+
+ String src = new String(Files.readAllBytes(source.toPath()));
+
+ boolean hasRead = src.contains("public void readObject");
+ boolean hasWrite = src.contains("public void writeObject");
+ boolean hasReadDefault = DEFAULT_READ.matcher(src).find();
+ boolean hasWriteDefault = DEFAULT_WRITE.matcher(src).find();
+
+ // if either method is hand-written custom streaming (present, but NOT a defaultXObject
+ // pass-through), the class fully manages its own streaming and is already deterministic;
+ // leave it entirely alone rather than risk a generated/hand-written mismatch
+ if ((hasRead && !hasReadDefault) || (hasWrite && !hasWriteDefault)) {
+ return;
+ }
+
+ // build whole methods for any streaming direction that has no method at all
+ StringBuilder methods = new StringBuilder();
+ if (!hasRead) {
+ methods.append(FLAT_READ_OPEN);
+ for (Field field : fields) {
+ methods.append(" ").append(flatReadStatement(field, "ins")).append("\n");
+ }
+ methods.append(READ_CLOSE);
+ }
+ if (!hasWrite) {
+ if (methods.length() > 0) {
+ methods.append("\n");
+ }
+ methods.append(FLAT_WRITE_OPEN);
+ for (Field field : fields) {
+ methods.append(" ").append(flatWriteStatement(field, "out")).append("\n");
+ }
+ methods.append(WRITE_CLOSE);
+ }
+
+ boolean changed = false;
+ // replace a pass-through default read/write call in place, preserving surrounding logic
+ if (hasReadDefault) {
+ src = substituteDefault(src, DEFAULT_READ, fields, true);
+ changed = true;
+ }
+ if (hasWriteDefault) {
+ src = substituteDefault(src, DEFAULT_WRITE, fields, false);
+ changed = true;
+ }
+ // insert whole methods just before the class-closing brace, so we NEVER disturb the
+ // "// AUTO-GENERATED: METHODS" section (which holds the gendobj/genservice accessors) —
+ // SourceFile.generate() would replace that section wholesale and drop the accessors
+ if (methods.length() > 0) {
+ src = insertBeforeClassClose(src, methods.toString());
+ changed = true;
+ }
+ if (changed) {
+ System.err.println("Converting (flatten) " + sclass.getName() + "...");
+ writeFile(source.getAbsolutePath(), src);
+ }
+ }
+
+ /**
+ * Inserts {@code methods} (already 4-space indented, newline-terminated) just before the final
+ * top-level class-closing brace of {@code src}.
+ */
+ protected String insertBeforeClassClose (String src, String methods)
+ {
+ String[] lines = src.split("\n", -1);
+ int lastBrace = -1;
+ for (int ii = 0; ii < lines.length; ii++) {
+ if (lines[ii].trim().equals("}")) {
+ lastBrace = ii;
+ }
+ }
+ if (lastBrace < 0) {
+ throw new RuntimeException("No class-closing brace found");
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int ii = 0; ii < lines.length; ii++) {
+ if (ii == lastBrace) {
+ sb.append("\n").append(methods);
+ }
+ sb.append(lines[ii]);
+ if (ii < lines.length - 1) {
+ sb.append("\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Replaces the single {@code recv.defaultReadObject()} / {@code recv.defaultWriteObject()}
+ * statement matched by {@code pat} with an explicit, same-indentation block that streams every
+ * field via {@link com.threerings.io.GenStreamUtil}, reusing the receiver variable name.
+ */
+ protected String substituteDefault (String src, Pattern pat, List fields, boolean read)
+ {
+ Matcher m = pat.matcher(src);
+ StringBuffer sb = new StringBuffer();
+ while (m.find()) {
+ String indent = m.group(1), recv = m.group(2);
+ StringBuilder block = new StringBuilder();
+ for (int ii = 0; ii < fields.size(); ii++) {
+ if (ii > 0) {
+ block.append("\n");
+ }
+ Field field = fields.get(ii);
+ block.append(indent).append(
+ read ? flatReadStatement(field, recv) : flatWriteStatement(field, recv));
+ }
+ m.appendReplacement(sb, Matcher.quoteReplacement(block.toString()));
+ }
+ m.appendTail(sb);
+ return sb.toString();
+ }
+
+ protected String flatReadStatement (Field field, String recv)
+ {
+ return "com.threerings.io.GenStreamUtil.readField(" +
+ field.getDeclaringClass().getCanonicalName() + ".class, \"" + field.getName() +
+ "\", this, " + recv + ");";
+ }
+
+ protected String flatWriteStatement (Field field, String recv)
+ {
+ return "com.threerings.io.GenStreamUtil.writeField(" +
+ field.getDeclaringClass().getCanonicalName() + ".class, \"" + field.getName() +
+ "\", this, " + recv + ");";
+ }
+
protected String toReadObject (Field field)
{
Class> type = field.getType();
@@ -196,6 +382,9 @@ public class GenStreamableTask extends GenTask
/** A list of filesets that contain tile images. */
protected ArrayList _filesets = Lists.newArrayList();
+ /** Whether to generate self-contained flattened methods (Android/ART support). */
+ protected boolean _flatten;
+
protected static final String READ_OPEN =
" // from interface Streamable\n" +
" public void readObject (ObjectInputStream ins)\n" +
@@ -209,4 +398,23 @@ public class GenStreamableTask extends GenTask
" throws IOException\n" +
" {\n";
protected static final String WRITE_CLOSE = " }\n";
+
+ // fully-qualified signatures for flatten mode so no imports are required in the target file
+ protected static final String FLAT_READ_OPEN =
+ " // from interface Streamable\n" +
+ " public void readObject (com.threerings.io.ObjectInputStream ins)\n" +
+ " throws java.io.IOException, java.lang.ClassNotFoundException\n" +
+ " {\n";
+ protected static final String FLAT_WRITE_OPEN =
+ " // from interface Streamable\n" +
+ " public void writeObject (com.threerings.io.ObjectOutputStream out)\n" +
+ " throws java.io.IOException\n" +
+ " {\n";
+
+ /** Matches a pass-through {@code recv.defaultReadObject();} statement (captures indent, recv). */
+ protected static final Pattern DEFAULT_READ =
+ Pattern.compile("(?m)^([ \\t]*)(\\w+)\\.defaultReadObject\\s*\\(\\s*\\)\\s*;[ \\t]*$");
+ /** Matches a pass-through {@code recv.defaultWriteObject();} statement. */
+ protected static final Pattern DEFAULT_WRITE =
+ Pattern.compile("(?m)^([ \\t]*)(\\w+)\\.defaultWriteObject\\s*\\(\\s*\\)\\s*;[ \\t]*$");
}
diff --git a/tools/src/main/resources/com/threerings/presents/tools.properties b/tools/src/main/resources/com/threerings/presents/tools.properties
index c5dddd3fa..d60189f44 100644
--- a/tools/src/main/resources/com/threerings/presents/tools.properties
+++ b/tools/src/main/resources/com/threerings/presents/tools.properties
@@ -1,4 +1,5 @@
gendobj=com.threerings.presents.tools.GenDObjectTask
+genstreamable=com.threerings.presents.tools.GenStreamableTask
genservice=com.threerings.presents.tools.GenServiceTask
genreceiver=com.threerings.presents.tools.GenReceiverTask
instream=com.threerings.presents.tools.InstrumentStreamableTask