Features

Struct Types

Struct types lets you define data types which use as little memory as possible.

You can define a struct type by annotating it with @Struct annotation.

@Struct
public class Vec3 {
    public float x,y,z;
}

Struct Vec3 has a size of 12 bytes. You can double check this by running:

System.out.println("Size: " + Mem.sizeOf(Vec3.class));
System.out.println(Mem.layoutString(Vec3.class));

Output

Size: 12
~Struct test.HelloStruct$Vec3(12/12/12) Align: 4 ~
|0:x FLOAT(4), -1|
|4:y FLOAT(4), -1|
|8:z FLOAT(4), -1|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Automatic data alignment

For information about data alignment see.

@Struct
public class MixedData {
    byte Data1;
    short Data2;
    int Data3;
    byte Data4;
}

Struct MixedData does not have its data aligned. JUnion detects this and realigns the data automatically. We can once again use the following to check the struct size and layout.

System.out.println("Size: " + Mem.sizeOf(MixedData.class));
System.out.println(Mem.layoutString(MixedData.class));

Output

Size: 8
~Struct test.HelloStruct$MixedData(8/8/8) Align: 4 ~
|0:Data3 INT(4), -1|
|4:Data2 SHORT(2), -1|
|6:Data1 BYTE(1), -1|
|7:Data4 BYTE(1), -1|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The Data3 stars at index 0 which is a multiple of four, thus is aligned. Similarly Data2 starts at index 4 which is a multiple of 2, being aligned as well.

Manual data alignment

If your code depends on the layout of data within a struct, use the annotation property autopad.

@Struct(autopad=false)
public class Manual {
    public byte b;
    private byte padding;
    public char ch;
}

By setting autopad to false, you are required to define your data so that it is aligned. The compiler will throw an error if a structure is not properly aligned.

Creating Arrays of Struct Types

Struct arrays are allocated on the heap and are automatically freed.

Vec3[] arr = new Vec3[12];
arr[5].y = 10;
		
System.out.println("arr[5].y = " + arr[5].y );

64-bit long addressable arrays

Struct arrays support addressing with longs.

Vec3[] arr = new Vec3[Mem.li(1000000000L)];
arr[Mem.li(900000000L)].y = 10;
		
System.out.println("y = " + arr[Mem.li(900000000L)].y );

Modifying Native DirectByteBuffers

Not only is accessing/modifying bytebuffers more readable with struct syntax, it also improves performance.

ByteBuffer a = ByteBuffer.allocateDirect(num*vec3Size).order(ByteOrder.nativeOrder());

Vec3[] av = Mem.wrap(a, Mem.sizeOf(Vec3.class));

Index Checking

Vec3[] arr = new Vec3[12];

arr[-1].x = 5; //throws IndexOutOfBoundsException
arr[12].x = 5; //throws IndexOutOfBoundsException

Nested Structures

@Struct
public class Line2 {
    public Vec3 a, b;
}

Layout

Size: 24
~Struct test.HelloStruct$Line2(24/24/24) Align: 4 ~
|0:a STRUCT(4), -1|
|12:b STRUCT(4), -1|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Structure References

When building tree structures you may write something like.

@Struct
public class Node {
    public Node left, right;
}

However, a structure cannot contain itself. JUnion detects this error as a Circular Dependence.

To build tree structures we can write:

@Struct
public class Node {
    @Reference public Node left, right;
}

Null Reference Checking

Node[] n = new Node[10];
Node a = n[0].left; //throws NullPointerDereference

To check if a reference is null, use Mem.isNull

Node[] n = new Node[10];
if(!Mem.isNull(n[0].left)) {
    Node a = n[0].left;
}

Array Slices

You can create array slices. Slices share data, thus changes in slice are reflected in the original array.

Vec3[] arr = new Vec3[10];
for(int i = 0; i < arr.length; i++) arr[i].x = i;
		
Vec3[] a = Mem.slice(arr, 5, arr.length);
Vec3[] b = Mem.slice(arr, 5, arr.length, 2);
Vec3[] reversed = Mem.slice(arr, 0, arr.length, -1);
		
System.out.print("a:\t");
for(int i = 0; i < a.length; i++) System.out.print(a[i].x + ",");
System.out.print("\nb:\t");
for(int i = 0; i < b.length; i++) System.out.print(b[i].x+",");
System.out.print("\nrev:\t");
for(int i = 0; i < reversed.length; i++) System.out.print(reversed[i].x+",");

Output

a:	5.0,6.0,7.0,8.0,9.0,
b:      5.0,7.0,
rev:	9.0,8.0,7.0,6.0,5.0,4.0,3.0,2.0,1.0,0.0,

Generics

Vec3[] arr = new Vec3[10];
for(int i = 0; i < arr.length; i++) arr[i].x = i;

ArrayList<Vec3> list = new ArrayList<>();
list.add(arr[5]);
list.add(arr[1]);
list.add(arr[7]);

for(Vec3 v : list) {
    System.out.println(v.x);
}

Output

5.0
1.0
7.0

Stack Allocation

There are two ways to allocate structs on stack. The first safe way requires initialization of all struct variables with the following syntax.

Vec3 v = Mem.stack(Vec3.class); {
    v.x = 1; v.y = 2; v.z = 3;
}

If initialization is not desired, eg. you’d like to initialize the struct from a function call. For now this is possible as follow. This way is not safe, if you read the data before you initalize it.

Vec3 v = Mem0.stackRaw(Vec3.class);