Edit: Latest version:
Usage:
@MappedType(sizeof = 12)
public class MappedVec3 extends MappedObject
{
public float x;
public float y;
public float z; // you can hardcode this field's byte-offset using: @MappedField(offset=8)
public float length()
{
return (float) Math.sqrt(x*x + y*y + z*z); // natural field access
}
@Override
public String toString()
{
return "[" + x + "," + y + "," + z + "]";
}
}
public class TestMappedObject
{
public static void main(String[] args)
{
MappedObjectTransformer.register(MappedVec3.class);
// we 'fork' the current application. the forked version will have transformed classes
if (MappedObjectClassLoader.fork(TestMappedObject.class, args))
{
// if this is the version of the classloader without transformed classes, stop right here
return;
}
// business as usual
ByteBuffer bb = ByteBuffer.allocateDirect(4096);
bb.order(ByteOrder.nativeOrder());
MappedVec3 vecs = MappedVec3.map(bb);
// naturally we start at mapping 0
vecs.x = 1.30f;
vecs.y = 2.50f;
vecs.z = 3.60f;
vecs.view = 1; // go to mapping at index 1
vecs.x = 0.13f;
vecs.y = 0.25f;
vecs.z = 0.36f;
float x0 = bb.getFloat(0 << 2);
float y0 = bb.getFloat(1 << 2);
float z0 = bb.getFloat(2 << 2);
float x1 = bb.getFloat(3 << 2);
float y1 = bb.getFloat(4 << 2);
float z1 = bb.getFloat(5 << 2);
}
}
Everything below this line is outdated…
I decided to give implementing MappedObjects another try, after the previous attempt went down the drain last year.
Let’s say you have this ‘struct’ in C (yes, my C-skills are somewhere below sea level):
struct Sphere
{
float x, y, z, r;
bool intersects(Sphere that)
{
float xd = this->x - that->x;
float yd = this->y - that->y;
float zd = this->z - that->z;
float d2 = (xd * xd) + (yd * yd) + (zd * zd);
float r = this->r + that->r;
return d2 <= r * r;
}
}
Currently you’d have to fiddle with FloatBuffers or float[]s with offsets and strides, which is extremely boring and error-phrone to code. I wrote an API that does this for you, which a nice layer over it so that is supports byte[]s, direct and heap ByteBuffers. Last but not least, it’s extremely quick! Working with float[]s instead of byte[]s gains you only 5-7% performance, which it probably a good tradeoff for 99.99% of us.
Unfortunately, we still need to tell Java which fields have which offset, so… the code to construct the MappedObject is a bit verbose. But as we define the abstract class (or interface), we can simply talk to the interface, so that we keep all those nice features in our IDEs.
In the Java implementation it becomes:
@MappedType(stride = 16)
public abstract class Sphere // or interface...
{
// sliding window
public abstract void map(int element);
// getters (with annotations)
public abstract @MappedField(offset = 0, type = float.class) float x();
public abstract @MappedField(offset = 4, type = float.class) float y();
public abstract @MappedField(offset = 8, type = float.class) float z();
public abstract @MappedField(offset = 12, type = float.class) float r();
// setters
public abstract void x(float x);
public abstract void y(float y);
public abstract void z(float z);
public abstract void r(float r);
public boolean intersects(Sphere that)
{
float xd = this.x() - that.x();
float yd = this.y() - that.y();
float zd = this.z() - that.z();
float d2 = (xd * xd) + (yd * yd) + (zd * zd);
float r = this.r() + that.r();
return d2 <= r * r;
}
}
So much for the boiler-plate! Now it’s time to make a MappedObject over a byte[] or a ByteBuffer:
MappedObjectProvider<Sphere> provider = new MappedObjectProvider<Sphere>(Sphere.class);
int sphereCount = 128;
ByteBuffer data1 = ByteBuffer.allocate( provider.sizeof() * sphereCount);
ByteBuffer data2 = ByteBuffer.allocateDirect(provider.sizeof() * sphereCount);
byte[] data3 = new byte[ provider.sizeof() * sphereCount];
Sphere sA = provider.newMappedObject(data1, 0, sphereCount); // datasource, base, elementCount
Sphere sB = provider.newMappedObject(data2, 0, sphereCount);
Sphere sC = provider.newMappedObject(data3, 0, sphereCount);
sA.map(5); // slide the MappedObject to element 5 (offset = base+5*stride)
sB.map(13); // slide the MappedObject to element 13
// sA.x = sB.x;
sA.x(sB.x());
The sourcecode (and binaries) are here:
http://213.247.55.3/~balk1242/html_stuff/ (for browsable packages, with source in formatted HTML)
jawnae.mapped.PrimitiveConverter
Although I’m using sun.misc.Unsafe to write all primitives in the byte[] at ‘native speed’, it’s impossible to get a buffer overflow.
There are automatic optimisations when stride is power-of-two, which makes is roughly 40% faster.
Give it a try and let me known what you think!