-
Notifications
You must be signed in to change notification settings - Fork 115
Packed Weaver
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.
- 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>)
.
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.
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)
.
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)
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);
for (int i = 0, s = $data.capacity(); s > i; i++)
newBuffer.put(i, $data.get(i));
$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);
}
}
- Adam Martin's Data Structures for Entity Systems: Contiguous memory.
- Overview
- Concepts
- Getting Started
- Using
- More guides
- Plugins
- Game Gallery
- Tools and Frameworks
- API reference