A continuation of the popular Artemis ECS framework
BREAKING CHANGES
2.8.2
.1.9.10
.Fluid entities quality of life changes.
EntitySubscription
facade ESubscription
, for convenient E
iteration.ESubscription
(requires registered FluidEntityPlugin
)@FluidMethod(exclude=true)
E
toString now reports entity id for easier debugging. ie: E{id=10}
.Gradle plugin now supports classesDirs
, deprecated classesDir
.
Improve flexibility of logic behind annotations dependency injection. (no user impact).
FIX: Avoiding an NPE in optimizer.EntitySystemType if meta.superClass is null.
FIX: ObjectWeb ASM updated to 7.0 to avoid choking on more modern bytecode.
FIX: Artemis-odb integration tests now run properly on JDK 9+.
FIX: Fluid gradle plugin logging (now reports what artifacts it scans).
BREAKING CHANGES
2.8.0
(LibGDX is blocking 2.8.2
).1.9.9
.Global option to delay component removal until all subscriptions have been notified.
new WorldConfigurationBuilder().alwaysDelayComponentRemoval(true)
Annotation driven aspects (@All
, @Exclude
, @One
).
Archetype
, Aspect
, Aspect.Builder
and EntitySubscription
fields.Fluid entities quality of life changes.
FluidEntityPlugin
instead of SuperMapper
.FluidEntityPlugin
generated as part of the fluid generation process.FluidIteratingSystem
.for ( E e : E.withGroup("enemy") ) { .. }
E player = E.withTag("player");
for ( E e : E.withAspect(Aspect.all(Pickup.class, Pos.class)) ) { .. }
for ( E e : E.withComponent(Pickup.class) ) { .. }
Pos2
? Now you can!)Support for abstract plugins with multiple possible implementations. For example, a logging plugin might have multiple implementations (TinyLog, SLF4J+??, LibGDX). As a plugin developer, we want to be able to refer to the generic facade (LoggingPlugin) and throw a warning when no specific implementation has been registered.
Fix: Better document side effects of ComponentMapper and EntitySubscription.
Fix: Unchecked warning in SuperMapper
.
Fix: ArtemisMultiException was missing no-args ctor (req by serializaion)
World
instances can now inject themselves (World::inject
)
EntitySubscription::removeSubscriptionListener
.false
Six release candidates and 4 months later, 2.0.0 emerges from the rabbit hole. A lot has been internally reworked - without inflicting too much change to the old API; most breaking changes are limited to functionality on the periphery of the framework - supposedly with minimal impact (SystemInvocationStrategy) on existing code.
An optional system for automatically tracking relationships between entities. Components with fields referencing other entities are automatically registered and tracked; per-field listeners can supplement the system with additional logic.
Generally useful for editors/tooling - where unexpected entity and composition changes can occur. This system provides a way to ensure a consistent state without too much hooman/computer overhead.
// must be explicitly registered with the world
EntityLinkManager elm = world.getSystem(EntityLinkManager.class);
elm.register(InheritScale.class, new LinkAdapter() {
// LinkListeners are subject to dependency injection upon registration
private ComponentMapper<InheritScale> mapper;
@Override
public void onTargetDead(int sourceId, int deadTargetId) {
// target is dead - remove component
mapper.remove(sourceId);
}
});
... where InheritScale
:
@PooledWeaver
public class InheritScale extends Component {
@EntityId public int target = -1;
}
@DelayedComponentRemoval extends the lifecycle of component types, ensuring removed instances are retrievable until all SubscriptionListener#removed(IntBag)
have been notified - regardless of removal method.
world.getAspectSubscriptionManager()
.get(Aspect.all(ProlongedLife.class))
.addSubscriptionListener(new SubscriptionListener() {
@Override
public void inserted(IntBag entities) {}
@Override
public void removed(IntBag entities) {
for (int i = 0, s = entities.size(); s > i; i++) {
// without @DelayedComponentRemoval on ProlongedLife,
// this may be null
ProlongedLife pf = prolongedLifeMapper.get(entities.get(i));
}
}
});
Decorate aspect-related fields with @AspectDescriptor to have them automatically injected as part of the normal injection mechanism. It's a bit situational, but can assist in keeping multi-subscription systems and such a bit cleaner.
@AspectDescriptor(
all = {Scale.class, Tint.class},
exclude = Velocity.class)
private EntityTransmuter transmuter;
@AspectDescriptor
works with:
Archetype
Aspect
Aspect.Builder
EntitySubscription
EntityTransmuter
We now have a kryo serializer thanks to piotr-j. Output size is roughly 2/3 that of json, but custom component serializers can bring the size down further.
artemis-odb-serializer
is the new base package from which all serializer implementations inherit. It should now be bit easier to write custom serializers. However, I don't think it's a requested feature, so the API may need an iteration or two more before it's feasible - if desired, create an issue.
All java.util.BitSet
usage replaced by BitVector
- it performs better than BitSet when decoding bits-to-IntBag, especially when dealing with high population counts. This mainly affects the performance of entity subscriptions when propagating changes to systems and listeners.
That said, reliance on bitsets have decreased as all composition changes work directly against the composition id (a compositionId
is mapped to each unique component composition/bitset). In 1.4.0 and earlier, EntityEdit
resulted in a somewhat costly bitset-to-compositionId translation - this is no longer the case. As such, expect to see somewhat higher compositionId
numbers and faster performance.
There should not be anything major affecting existing side. Custom implementations of
SystemInvocationStrategy
need to be updated however.
InvocationStrategy::process
InvocationStrategy::updateEntityStates
must be called before processing the first system.
Previously, the initial update was done by the world instance.BaseSystem
; this should only have
implication for custom implementations of SystemInvocationStrategy
.Injector#getRegistered(Class|String)
BaseSystem#process
will now run the system, even if setEnabled(false)
has been called. SystemInvocationStrategy
now tracks which systems are enabled/disabled.PackedComponent
and @PackedWeaver
.IntBag::get(index)
, ArrayIndexOutOfBoundsException reported size of backing array, not logical size.java.util.BitSet
.
unsafeGet
, unsafeSet
, unsafeClear
require that the underlying
array can contain the bit index.BitVector::ensureCapacity(int bits)
explicitly grows the bit vector.
Typically used together with the unsafe-
methods.EntityManager::registerEntityStore(BitVector)
- when representing
entity id:s as bits, makes unsafe-
methods safe - as the EntityManager
grows the bit vector as necessary.InvocationStrategy::process
InvocationStrategy::updateEntityStates
must be called before processing the first system.
Previously, the initial update was done by the world instance.ComponentManager::compositionIdentity(BitSet)
now public.@AspectDescriptor
now also valid on Archetypes.EntityLinkManager
fires events for initial entities when registering a LinkListener;
toggleable in constructor.BaseSystem
; this should only have
implication for custom implementations of SystemInvocationStrategy
.@AspectDescriptor
on appropriate fields to injectAspect
, Aspect.Builder
, EntityTransmuter
, EntitySubscription
EntityTransmuter(World, Aspect.Builder)
constructorWorld#compositionId(entityId)
added. Previously, one had to go
via Entity#getCompositionId()
EntityManager#reset
- if the world is empty, resets entity id generation to 0
AspectSubscriptionManager
before informing listeners.Injector#getRegistered(Class|String)
ComponentMapper#getSafe
deprecated, #get
is sufficient for all use-cases now.
due to mappers always growing their backing arrays to accomodate the highest entity id.BaseSystem#process
will now run the system, even if setEnabled(false)
has been called.
SystemInvocationStrategy
now tracks which systems are enabled/disabled.
(you may want to update your custom SystemInvocationStrategy
implementations).@EntityId int
, Entity
, @EntityId IntBag
, Bag<Entity>
(shares behavior with serialization).LinkListener
for listening in on when links between entities are established, changed or disconnected.@LinkPolicy
, applied on component fields referencing entities.@DelayedComponentRemoval
guarantees that component is available in SubscriptionListener#removed(IntBag)
.World#getRegistered
, retrieves injectable objects programmatically.EntityEdit
logic, less code and more performance.PackedComponent
and @PackedWeaver
.AspectSubscriptionManager#getSubscriptions
Bag(Class<T>)
and Bag(Class<T>, int capacity)
IntBag#get
throws ArrayIndexOutOfBoundsException
whenever index
is greater than the reported size,
regardless of the size of the underlying array.artemis-odb-serializer
artifact, used by all serialization backends,IntBag::get(index)
, ArrayIndexOutOfBoundsException reported size of backing array, not logical size.java.util.BitSet
.
unsafeGet
, unsafeSet
, unsafeClear
require that the underlying
array can contain the bit index.BitVector::ensureCapacity(int bits)
explicitly grows the bit vector.
Typically used together with the unsafe-
methods.EntityManager::registerEntityStore(BitVector)
- when representing
entity id:s as bits, makes unsafe-
methods safe - as the EntityManager
grows the bit vector as necessary.InvocationStrategy::process
InvocationStrategy::updateEntityStates
must be called before processing the first system.
Previously, the initial update was done by the world instance.ComponentManager::compositionIdentity(BitSet)
now public.@AspectDescriptor
now also valid on Archetypes.EntityLinkManager
fires events for initial entities when registering a LinkListener;
toggleable in constructor.BaseSystem
; this should only have
implication for custom implementations of SystemInvocationStrategy
.@AspectDescriptor
on appropriate fields to injectAspect
, Aspect.Builder
, EntityTransmuter
, EntitySubscription
EntityTransmuter(World, Aspect.Builder)
constructorWorld#compositionId(entityId)
added. Previously, one had to go
via Entity#getCompositionId()
EntityManager#reset
- if the world is empty, resets entity id generation to 0
AspectSubscriptionManager
before informing listeners.Injector#getRegistered(Class|String)
ComponentMapper#getSafe
deprecated, #get
is sufficient for all use-cases now.
due to mappers always growing their backing arrays to accomodate the highest entity id.BaseSystem#process
will now run the system, even if setEnabled(false)
has been called.
SystemInvocationStrategy
now tracks which systems are enabled/disabled.
(you may want to update your custom SystemInvocationStrategy
implementations).@EntityId int
, Entity
, @EntityId IntBag
, Bag<Entity>
(shares behavior with serialization).LinkListener
for listening in on when links between entities are established, changed or disconnected.@LinkPolicy
, applied on component fields referencing entities.@DelayedComponentRemoval
guarantees that component is available in SubscriptionListener#removed(IntBag)
.World#getRegistered
, retrieves injectable objects programmatically.EntityEdit
logic, less code and more performance.PackedComponent
and @PackedWeaver
.AspectSubscriptionManager#getSubscriptions
Bag(Class<T>)
and Bag(Class<T>, int capacity)
IntBag#get
throws ArrayIndexOutOfBoundsException
whenever index
is greater than the reported size,
regardless of the size of the underlying array.artemis-odb-serializer
artifact, used by all serialization backends,java.util.BitSet
.
unsafeGet
, unsafeSet
, unsafeClear
require that the underlying
array can contain the bit index.BitVector::ensureCapacity(int bits)
explicitly grows the bit vector.
Typically used together with the unsafe-
methods.EntityManager::registerEntityStore(BitVector)
- when representing
entity id:s as bits, makes unsafe-
methods safe - as the EntityManager
grows the bit vector as necessary.InvocationStrategy::process
InvocationStrategy::updateEntityStates
must be called before processing the first system.
Previously, the initial update was done by the world instance.ComponentManager::compositionIdentity(BitSet)
now public.@AspectDescriptor
now also valid on Archetypes.EntityLinkManager
fires events for initial entities when registering a LinkListener;
toggleable in constructor.BaseSystem
; this should only have
implication for custom implementations of SystemInvocationStrategy
.@AspectDescriptor
on appropriate fields to injectAspect
, Aspect.Builder
, EntityTransmuter
, EntitySubscription
EntityTransmuter(World, Aspect.Builder)
constructorWorld#compositionId(entityId)
added. Previously, one had to go
via Entity#getCompositionId()
EntityManager#reset
- if the world is empty, resets entity id generation to 0
AspectSubscriptionManager
before informing listeners.Injector#getRegistered(Class|String)
ComponentMapper#getSafe
deprecated, #get
is sufficient for all use-cases now.
due to mappers always growing their backing arrays to accomodate the highest entity id.BaseSystem#process
will now run the system, even if setEnabled(false)
has been called.
SystemInvocationStrategy
now tracks which systems are enabled/disabled.
(you may want to update your custom SystemInvocationStrategy
implementations).@EntityId int
, Entity
, @EntityId IntBag
, Bag<Entity>
(shares behavior with serialization).LinkListener
for listening in on when links between entities are established, changed or disconnected.@LinkPolicy
, applied on component fields referencing entities.@DelayedComponentRemoval
guarantees that component is available in SubscriptionListener#removed(IntBag)
.World#getRegistered
, retrieves injectable objects programmatically.EntityEdit
logic, less code and more performance.PackedComponent
and @PackedWeaver
.AspectSubscriptionManager#getSubscriptions
Bag(Class<T>)
and Bag(Class<T>, int capacity)
IntBag#get
throws ArrayIndexOutOfBoundsException
whenever index
is greater than the reported size,
regardless of the size of the underlying array.artemis-odb-serializer
artifact, used by all serialization backends,InvocationStrategy::process
InvocationStrategy::updateEntityStates
must be called before processing the first system.
Previously, the initial update was done by the world instance.ComponentManager::compositionIdentity(BitSet)
now public.@AspectDescriptor
now also valid on Archetypes.EntityLinkManager
fires events for initial entities when registering a LinkListener;
toggleable in constructor.BaseSystem
; this should only have
implication for custom implementations of SystemInvocationStrategy
.@AspectDescriptor
on appropriate fields to inject.Aspect
, Aspect.Builder
, EntityTransmuter
, EntitySubscription
EntityTransmuter(World, Aspect.Builder)
constructorWorld#compositionId(entityId)
added. Previously, one had to go
via Entity#getCompositionId()
EntityManager#reset
- if the world is empty, resets entity id generation to 0
AspectSubscriptionManager
before informing listeners.Injector#getRegistered(Class|String)
ComponentMapper#getSafe
deprecated, #get
is sufficient for all use-cases now.
due to mappers always growing their backing arrays to accomodate the highest entity id.BaseSystem#process
will now run the system, even if setEnabled(false)
has been called.
SystemInvocationStrategy
now tracks which systems are enabled/disabled.
(you may want to update your custom SystemInvocationStrategy
implementations).@EntityId int
, Entity
, @EntityId IntBag
, Bag<Entity>
(shares behavior with serialization).LinkListener
for listening in on when links between entities are established, changed or disconnected.@LinkPolicy
, applied on component fields referencing entities.@DelayedComponentRemoval
guarantees that component is available in SubscriptionListener#removed(IntBag)
.World#getRegistered
, retrieves injectable objects programmatically.EntityEdit
logic, less code and more performance.PackedComponent
and @PackedWeaver
.AspectSubscriptionManager#getSubscriptions
Bag(Class<T>)
and Bag(Class<T>, int capacity)
IntBag#get
throws ArrayIndexOutOfBoundsException
whenever index
is greater than the reported size,
regardless of the size of the underlying array.artemis-odb-serializer
artifact, used by all serialization backends,It's now possible to inject aspect-related classes:
public class SomeSystem extends BaseSystem {
@AspectDescriptor(
all = {ComponentX.class, ComponentY.class},
exclude = PooledString.class,
one = EntityHolder.class)
private EntitySubscription subcription;
@AspectDescriptor(
all = {ComponentX.class, ComponentY.class},
exclude = ComponentZ.class)
private EntityTransmuter transmuter;
@AspectDescriptor(
all = {ComponentX.class, ComponentY.class},
exclude = PooledString.class,
one = {ReusedComponent.class, EntityHolder.class})
private Aspect aspect;
Systems are automatically injected during world initialization. Use World#inject
for non-artemis types.
BaseSystem
; this should only have
implication for custom implementations of SystemInvocationStrategy
.@AspectDescriptor
on appropriate fields to inject.Aspect
, Aspect.Builder
, EntityTransmuter
, EntitySubscription
EntityTransmuter(World, Aspect.Builder)
constructorWorld#compositionId(entityId)
added. Previously, one had to go
via Entity#getCompositionId()
EntityManager#reset
- if the world is empty, resets entity id generation to 0
AspectSubscriptionManager
before informing listeners.Injector#getRegistered(Class|String)
ComponentMapper#getSafe
deprecated, #get
is sufficient for all use-cases now.
due to mappers always growing their backing arrays to accomodate the highest entity id.BaseSystem#process
will now run the system, even if setEnabled(false)
has been called.
SystemInvocationStrategy
now tracks which systems are enabled/disabled.
(you may want to update your custom SystemInvocationStrategy
implementations).@EntityId int
, Entity
, @EntityId IntBag
, Bag<Entity>
(shares behavior with serialization).LinkListener
for listening in on when links between entities are established, changed or disconnected.@LinkPolicy
, applied on component fields referencing entities.@DelayedComponentRemoval
guarantees that component is available in SubscriptionListener#removed(IntBag)
.World#getRegistered
, retrieves injectable objects programmatically.EntityEdit
logic, less code and more performance.PackedComponent
and @PackedWeaver
.AspectSubscriptionManager#getSubscriptions
Bag(Class<T>)
and Bag(Class<T>, int capacity)
IntBag#get
throws ArrayIndexOutOfBoundsException
whenever index
is greater than the reported size,
regardless of the size of the underlying array.artemis-odb-serializer
artifact, used by all serialization backends,World#compositionId(entityId)
added. Previously, one had to go
via Entity#getCompositionId()
EntityManager#reset
- if the world is empty, resets entity id generation to 0
AspectSubscriptionManager
before informing listeners.New version starting to shape up. There will probably be a few more release candidates before the official 2.0.0. Upgrading from 1.x to 2.0.0 should be a relatively smooth operation - less invasive than when migrating to 1.0.0.
Be sure to check out the EntityLinkManager, and for retrieving those removed components in subscription listeners - @DelayedComponentRemoval
. Finally, the kryo serializer is in place now too (wiki entry coming soon.
Injector#getRegistered(Class|String)
ComponentMapper#getSafe
deprecated, #get
is sufficient for all use-cases now.
due to mappers always growing their backing arrays to accomodate the highest entity id.BaseSystem#process
will now run the system, even if setEnabled(false)
has been called.
SystemInvocationStrategy
now tracks which systems are enabled/disabled.
(you may want to update your custom SystemInvocationStrategy
implementations).@EntityId int
, Entity
, @EntityId IntBag
, Bag<Entity>
(shares behavior with serialization).LinkListener
for listening in on when links between entities are established, changed or disconnected.@LinkPolicy
, applied on component fields referencing entities.@DelayedComponentRemoval
guarantees that component is available in SubscriptionListener#removed(IntBag)
.World#getRegistered
, retrieves injectable objects programmatically.EntityEdit
logic, less code and more performance.PackedComponent
and @PackedWeaver
.AspectSubscriptionManager#getSubscriptions
Bag(Class<T>)
and Bag(Class<T>, int capacity)
IntBag#get
throws ArrayIndexOutOfBoundsException
whenever index
is greater than the reported size,
regardless of the size of the underlying array.artemis-odb-serializer
artifact, used by all serialization backends,