Skip to content
Adrian Papari edited this page May 26, 2014 · 39 revisions

Weaving off-heap faux structs with @PackedWeaver

Packed components offer a way to lay out components contiguously in memory. This oftentimes involves a lot of boilerplate and inconvenient obfuscation from too many getters/setters/mutator methods.

@PackedWeaver offers a simple way of achieving a more memory-friendly layout without introducing any of the boilerplate usually associated with these kind of optimizations.

Component requirements

  • Only primitive types are permitted for fields. Objects aren't welcome.
  • Zero-argument/default constructor.
  • Must be added to entities with Entity#createComponent(Class<Component>).

Begin weaving

Assuming artemis-odb-maven-plugin is properly configured, simply annotate the component with @PackedWeaver:

@PackedWeaver
public class ExampleComponent extends Component {
	public float x;
	public float y;

	public void set(Vec2f vec) {
		this.x,= vec.x;
		this.y = vec.y;
	}
}

Direct field access is permitted - even encouraged. By not using getters/setters for accessing component values, one can write exampleComponent.x += 4 instead of exampleComponent.setX(exampleComponent.getX() + 4).

During compilation, each class accessing woven components are rewritten to act on the generated methods. Ergo, getters/setters are still invoked, it's just happening behind the scenes.

Playing nice with IDE:s

Most IDE editors are tightly coupled with the compiler. Eclipse tends to bail out once it notices that fields have gone missing, but still thinks that classes are trying to invoke those.

To overcome this, tell artemis to keep the original fields in the components (they're just there to keep the IDE happy, they aren't actually used). Add the following to the pom.xml:

<properties>
	<artemis.ideFriendlyPacking>false</artemis.ideFriendlyPacking>
</properties>

<profiles>
	<profile>
		<id>ide</id>
		<properties>
			<artemis.ideFriendlyPacking>true</artemis.ideFriendlyPacking>
		</properties>
	</profile>
</profiles>

<build>
	<plugins>
		<plugin>
			<groupId>net.onedaybeard.artemis</groupId>
			<artifactId>artemis-odb-maven-plugin</artifactId>
			<version>0.6.0</version>
			<executions>
				<execution>
					<goals>
						<goal>artemis</goal> <!-- weaving -->
						<goal>matrix</goal> <!-- CDM report -->
					</goals>
					<configuration>
						<ideFriendlyPacking>${artemis.ideFriendlyPacking}</ideFriendlyPacking>
					</configuration>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>

Then configure the IDE to always run with the ide profile. In eclipse, it's under project properties -> Maven -> Active Maven Profiles (comma separated).

Referencing several components of the same type at once

Each ComponentMapper has its own instance of the PackedComponent which it is acting on. This can be problematic when two simultaneous references are required inside the same scope. To overcome this either create a 2nd mapper of the same type or tuck away an extra instance of the component type with ComponentMapper#get(entity, forceNewInstance)

Internal representation reference

Reusing the example from the top of the page:

@PackedWeaver
public class ExampleComponent extends Component {
	public float x;
	public float y;

	public void set(Vec2f vec) {
		this.x,= vec.x;
		this.y = vec.y;
	}
}

The above class looks something like this after it's been compiled:

public class ExampleComponent extends PackedComponent {

	private int $stride;
	private static final int $_SIZE_OF = 8; // float + float = 8 bytes
	private static ByteBuffer $data = ByteBuffer.allocateDirect(128 * $_SIZE_OF);


	@Override
	protected PackedComponent forEntity(Entity e) {
		this.$stride = $_SIZE_OF * e.getId();
		if (($data.capacity() - $_SIZE_OF) <= $stride) $grow();
		return this;
	}

	@Override
	protected void reset() {
		$data.putFloat($stride + 0, 0);
		$data.putFloat($stride + 4, 0);
	}

	private static void $grow() {
		ByteBuffer newBuffer = ByteBuffer.allocateDirect($data.capacity() * 2);
		newBuffer.put($data);
		$data = newBuffer;
	}

	public float x() {
		return $data.getFloat($stride + 0);
	}

	public float y() {
		return $data.getFloat($stride + 4);
	}

	public void x(float value) {
		$data.putFloat($stride + 0, value);
	}

	public void y(float value) {
		$data.putFloat($stride + 4, value);
	}

	public void set(Vec2f vec) {
		$data.putFloat($stride + 0, v.x);
		$data.putFloat($stride + 4, v.y);
	}
}

Resources

Clone this wiki locally