Added support for streaming arrays of streamables; extracted the efficient

streamable serialization code from PolyStreamableList and made it
generally available; modified the StreamableArrayList to support
heterogenous lists (because they don't have much more overhead than
homogenous lists now) which allows us to remove PolyStreamableList
entirely.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1140 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2002-03-20 22:58:26 +00:00
parent 357a8e5949
commit 1bf6e6093d
6 changed files with 319 additions and 177 deletions
@@ -1,5 +1,5 @@
//
// $Id: FieldMarshallerRegistry.java,v 1.13 2002/02/07 05:27:07 mdb Exp $
// $Id: FieldMarshallerRegistry.java,v 1.14 2002/03/20 22:58:26 mdb Exp $
package com.threerings.presents.io;
@@ -35,6 +35,9 @@ public class FieldMarshallerRegistry
if (Streamable.class.isAssignableFrom(clazz)) {
marsh = _streamableMarsh;
_registry.put(clazz, marsh);
} else if (STREAMARRAY_CLASS.isAssignableFrom(clazz)) {
marsh = _streamarrayMarsh;
_registry.put(clazz, marsh);
}
}
@@ -75,6 +78,14 @@ public class FieldMarshallerRegistry
protected static FieldMarshaller _streamableMarsh =
new StreamableFieldMarshaller();
/** Used for marshalling arrays of {@link Streamable}. */
protected static FieldMarshaller _streamarrayMarsh =
new StreamableArrayFieldMarshaller();
/** Used to identify streamable array instances. */
protected static final Class STREAMARRAY_CLASS =
(new Streamable[0]).getClass();
static {
// register our field marshallers
registerMarshaller(BooleanFieldMarshaller.class);
@@ -1,132 +0,0 @@
//
// $Id: PolyStreamableList.java,v 1.1 2002/02/12 01:54:51 shaper Exp $
package com.threerings.presents.io;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import com.samskivert.util.ListUtil;
import com.threerings.presents.Log;
/**
* A poly streamable list is a wrapper around an {@link ArrayList} that
* knows how to efficiently stream {@link Streamable} objects of
* potentially heterogenous classes. Accordingly, all objects inserted
* into the list must implement the {@link Streamable} interface. The
* list ordering is properly maintained when sending data over the wire.
*/
public class PolyStreamableList extends ArrayList
implements Streamable
{
// documentation inherited from interface
public void writeTo (DataOutputStream out)
throws IOException
{
// create a list used to map class names to class ids
Object[] cnames = new String[DEFAULT_SIZE];
// write out the size of the list
int size = size();
out.writeInt(size);
// write out each of the list elements
int nextIdx = 0;
for (int ii = 0; ii < size; ii++) {
// get the next streamable to write out
Object value = get(ii);
if (!(value instanceof Streamable)) {
throw new RuntimeException(
"Requested to serialize invalid type [value=" + value +
", type=" + value.getClass().getName() + "]");
}
Streamable s = (Streamable)value;
// look up the class id
String cname = s.getClass().getName();
int cidx = ListUtil.indexOfEqual(cnames, cname);
if (cidx == -1) {
// store this class name to class id mapping
cidx = nextIdx++;
cnames = ListUtil.add(cnames, cidx, cname);
// write out the class id and class name
out.writeInt(cidx);
out.writeUTF(cname);
} else {
// we need only write out the class id
out.writeInt(cidx);
}
// write out the object itself
s.writeTo(out);
}
}
// documentation inherited from interface
public void readFrom (DataInputStream in)
throws IOException
{
// create a list used to map class ids to class names
Object[] cnames = new String[DEFAULT_SIZE];
// read in the size of the list
int size = in.readInt();
// read in each of the list elements
for (int ii = 0; ii < size; ii++) {
// read the class id number
int cidx = in.readInt();
// look up the class name
String cname = (cidx > cnames.length - 1) ? null :
(String)cnames[cidx];
if (cname == null) {
// store this class id to class name mapping
cname = in.readUTF();
cnames = ListUtil.add(cnames, cidx, cname);
}
try {
// instantiate the streamable object
Class clazz = Class.forName(cname);
if (!Streamable.class.isAssignableFrom(clazz)) {
throw new RuntimeException(
"Requested to unserialize invalid type " +
"[type=" + cname + "]");
}
Streamable s = (Streamable)clazz.newInstance();
// read in the object itself
s.readFrom(in);
// and add it to the list
add(s);
} catch (ClassNotFoundException cnfe) {
throw new IOException(
"Unable to instantiate class [cname=" + cname +
", cnfe=" + cnfe + "]");
} catch (InstantiationException ie) {
throw new IOException(
"Unable to instantiate class [cname=" + cname +
", ie=" + ie + "]");
} catch (IllegalAccessException iae) {
throw new IOException(
"Unable to instantiate class [cname=" + cname +
", iae=" + iae + "]");
}
}
}
/** The default size of the class name lists used when serializing or
* unserializing the list. */
protected static final int DEFAULT_SIZE = 8;
}
@@ -0,0 +1,39 @@
//
// $Id: StreamableArrayFieldMarshaller.java,v 1.1 2002/03/20 22:58:26 mdb Exp $
package com.threerings.presents.io;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import com.threerings.presents.io.Streamable;
public class StreamableArrayFieldMarshaller implements FieldMarshaller
{
public void writeTo (DataOutputStream out, Field field, Object obj)
throws IOException, IllegalAccessException
{
Streamable[] values = (Streamable[])field.get(obj);
// in order to make use of the general purpose streamable
// serialization routines, we have to have a non-null array, so we
// create a zero length array if our field value is null
if (values == null) {
values = (Streamable[])Array.newInstance(
field.getType().getComponentType(), 0);
}
StreamableUtil.writeStreamables(out, values);
}
public void readFrom (DataInputStream in, Field field, Object obj)
throws IOException, IllegalAccessException
{
// read and set
field.set(obj, StreamableUtil.readStreamables(in));
}
}
@@ -1,5 +1,5 @@
//
// $Id: StreamableUtil.java,v 1.1 2001/12/03 23:48:38 mdb Exp $
// $Id: StreamableUtil.java,v 1.2 2002/03/20 22:58:26 mdb Exp $
package com.threerings.presents.io;
@@ -7,6 +7,11 @@ import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.lang.reflect.Array;
import com.samskivert.io.NestableIOException;
import com.samskivert.util.ListUtil;
/**
* Utility functions useful for {@link Streamable} implementations.
*/
@@ -67,4 +72,118 @@ public class StreamableUtil
}
return values;
}
/**
* Writes the supplied streamable array to the data output stream. The
* array cannot be null, but can be of zero length.
*/
public static void writeStreamables (
DataOutputStream out, Streamable[] values)
throws IOException
{
// create a list used to map class names to class ids
Object[] cnames = new Object[DEFAULT_SIZE];
// seed it with the component type of the array (which we write
// out first so that we can recreate the array on the other side)
String cname = values.getClass().getComponentType().getName();
cnames[0] = cname;
out.writeUTF(cname);
// write out the size of the list
int size = (values == null) ? 0 : values.length;
out.writeInt(size);
// write out each of the list elements
short nextIdx = 1;
for (int ii = 0; ii < size; ii++) {
// get the next streamable to write out
Streamable s = values[ii];
// look up the class id
cname = s.getClass().getName();
short cidx = (short)ListUtil.indexOfEqual(cnames, cname);
if (cidx == -1) {
// store this class name to class id mapping
cidx = nextIdx++;
cnames = ListUtil.add(cnames, cidx, cname);
// write out the class id and class name
out.writeShort(cidx);
out.writeUTF(cname);
} else {
// we need only write out the class id
out.writeShort(cidx);
}
// write out the object itself
s.writeTo(out);
}
}
/**
* Reads an array of streamables from the data input stream that was
* previously written via {@link #writeStreamables}.
*/
public static Streamable[] readStreamables (DataInputStream in)
throws IOException
{
// read in the component type and array size
String ctype = in.readUTF();
int size = in.readInt();
Streamable[] values = null;
// create the target array
try {
values = (Streamable[])
Array.newInstance(Class.forName(ctype), size);
} catch (Exception e) {
String errmsg = "Unable to instantiate streamable array " +
"[ctype=" + ctype + "]";
throw new NestableIOException(errmsg, e);
}
// create a list used to map class ids to class names
Object[] cnames = new Object[DEFAULT_SIZE];
// seed our class names with the component type
cnames[0] = ctype;
// read in each of the list elements
for (int ii = 0; ii < size; ii++) {
// read the class id number
short cidx = in.readShort();
// look up the class name
String cname = (cidx > cnames.length - 1) ? null :
(String)cnames[cidx];
if (cname == null) {
// store this class id to class name mapping
cname = in.readUTF();
cnames = ListUtil.add(cnames, cidx, cname);
}
try {
// instantiate the streamable object
Class clazz = Class.forName(cname);
Streamable s = (Streamable)clazz.newInstance();
// read in the object itself
s.readFrom(in);
// and add it to the list
values[ii] = s;
} catch (Exception e) {
String errmsg = "Error instantiating streamable array " +
"element [ctype=" + ctype + ", index=" + ii +
", etype=" + cname + "]";
throw new NestableIOException(errmsg, e);
}
}
return values;
}
/** The default size of the class name lists used when serializing or
* unserializing the list. */
protected static final int DEFAULT_SIZE = 8;
}
@@ -1,5 +1,5 @@
//
// $Id: StreamableArrayList.java,v 1.3 2002/03/20 19:07:15 mdb Exp $
// $Id: StreamableArrayList.java,v 1.4 2002/03/20 22:58:26 mdb Exp $
package com.threerings.presents.util;
@@ -10,15 +10,18 @@ import java.io.DataOutputStream;
import java.util.ArrayList;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.io.Streamable;
import com.threerings.presents.io.StreamableUtil;
/**
* Provides a means by which an ordered collection of streamable instances
* (all of the exact same class) can be delivered over the network. A
* streamable array list can be supplied anywhere that a distributed
* object value can be supplied, but bear in mind that once the list is
* created, it's elements cannot be changed without rebroadcasting the
* entire list. It is not like a {@link DSet} which allows individual
* elements to be added or removed.
* (of potentially heterogenous derived classes) can be delivered over the
* network. A streamable array list can be supplied anywhere that a
* distributed object value can be supplied, but bear in mind that once
* the list is created, it's elements cannot be changed without
* rebroadcasting the entire list. It is not like a {@link DSet} which
* allows individual elements to be added or removed, or
* <code>Streamable[]</code> distributed object fields for which elements
* can be individually updated.
*/
public class StreamableArrayList
extends ArrayList implements Streamable
@@ -27,48 +30,22 @@ public class StreamableArrayList
public void writeTo (DataOutputStream out)
throws IOException
{
int count = size();
out.writeInt(count);
// only write element info if we have elements
if (count > 0) {
Streamable first = (Streamable)get(0);
// write out the classname of our elements
out.writeUTF(first.getClass().getName());
// now write out our elements
for (int i = 0; i < count; i++) {
Streamable s = (Streamable)get(i);
s.writeTo(out);
}
}
// convert ourselves into an array and use streamable util to
// write it out
Streamable[] values = new Streamable[size()];
toArray(values);
StreamableUtil.writeStreamables(out, values);
}
// documentation inherited
public void readFrom (DataInputStream in)
throws IOException
{
// read in our element count
int count = in.readInt();
// only read in elements if we have some
if (count > 0) {
// read in the element class and load it up
String cname = in.readUTF();
try {
Class clazz = Class.forName(cname);
// now read in the elements
for (int i = 0; i < count; i++) {
Streamable s = (Streamable)clazz.newInstance();
s.readFrom(in);
add(s);
}
} catch (Exception e) {
throw new IOException("Error reading streamable " +
"[class=" + cname + ", err=" + e + "]");
}
// use streamable util to read in our values
Streamable[] values = StreamableUtil.readStreamables(in);
int vcount = values.length;
for (int i = 0; i < vcount; i++) {
add(values[i]);
}
}
}