
com.artemis.EntitySubscription Maven / Gradle / Ivy
Show all versions of artemis-odb Show documentation
package com.artemis;
import com.artemis.annotations.DelayedComponentRemoval;
import com.artemis.utils.Bag;
import com.artemis.utils.BitVector;
import com.artemis.utils.IntBag;
/**
* Maintains the list of entities matched by an aspect. Entity subscriptions
* are automatically updated during {@link com.artemis.World#process()}.
*
* Any {@link com.artemis.EntitySubscription.SubscriptionListener listeners}
* are informed when entities are added or removed.
*
* Be careful! Subscriptions do not immediately reflect changes by the
* active system. Subscriptions only guarantee contained entities matched
* before the current system started processing. Access entities and
* components defensively.
*/
public class EntitySubscription {
final SubscriptionExtra extra;
private final IntBag entities;
private final BitVector activeEntityIds;
private final BitVector insertedIds;
private final BitVector removedIds;
final BitVector aspectCache = new BitVector();
EntitySubscription(World world, Aspect.Builder builder) {
extra = new SubscriptionExtra(builder.build(world), builder);
activeEntityIds = new BitVector();
entities = new IntBag();
insertedIds = new BitVector();
removedIds = new BitVector();
EntityManager em = world.getEntityManager();
em.registerEntityStore(activeEntityIds);
em.registerEntityStore(insertedIds);
em.registerEntityStore(removedIds);
}
/**
* Returns a reference to the bag holding all matched entities.
*
* Warning: Never remove elements from the bag, as this
* will lead to undefined behavior.
*
* @return View of all active entities.
*/
public IntBag getEntities() {
if (entities.isEmpty() && !activeEntityIds.isEmpty())
rebuildCompressedActives();
return entities;
}
/**
* Returns the bitset tracking all matched entities.
*
* Warning: Never toggle bits in the bitset, as
* this may lead to erroneously added or removed entities.
*
* @return View of all active entities.
*/
public BitVector getActiveEntityIds() {
return activeEntityIds;
}
/**
* @return aspect used for matching entities to subscription.
*/
public Aspect getAspect() {
return extra.aspect;
}
/**
* @return aspect builder used for matching entities to subscription.
*/
public Aspect.Builder getAspectBuilder() {
return extra.aspectReflection;
}
/**
* A new unique component composition detected, check if this
* subscription's aspect is interested in it.
*/
void processComponentIdentity(int id, BitVector componentBits) {
aspectCache.ensureCapacity(id);
aspectCache.set(id, extra.aspect.isInterested(componentBits));
}
void rebuildCompressedActives() {
activeEntityIds.toIntBag(entities);
}
final void check(int id, int cid) {
boolean interested = aspectCache.unsafeGet(cid);
boolean contains = activeEntityIds.unsafeGet(id);
if (interested && !contains) {
insert(id);
} else if (!interested && contains) {
remove(id);
}
}
private void remove(int entityId) {
activeEntityIds.unsafeClear(entityId);
removedIds.unsafeSet(entityId);
}
private void insert(int entityId) {
activeEntityIds.unsafeSet(entityId);
insertedIds.unsafeSet(entityId);
}
void process(IntBag changed, IntBag deleted) {
deleted(deleted);
changed(changed);
informEntityChanges();
}
void processAll(IntBag changed, IntBag deleted) {
deletedAll(deleted);
changed(changed);
informEntityChanges();
}
void informEntityChanges() {
if (insertedIds.isEmpty() && removedIds.isEmpty())
return;
transferBitsToInts(extra.inserted, extra.removed);
extra.informEntityChanges();
entities.setSize(0);
}
private void transferBitsToInts(IntBag inserted, IntBag removed) {
insertedIds.toIntBag(inserted);
removedIds.toIntBag(removed);
insertedIds.clear();
removedIds.clear();
}
private void changed(IntBag entitiesWithCompositions) {
int[] ids = entitiesWithCompositions.getData();
for (int i = 0, s = entitiesWithCompositions.size(); s > i; i += 2) {
int id = ids[i];
boolean interested = aspectCache.unsafeGet(ids[i + 1]);
boolean contains = activeEntityIds.unsafeGet(id);
if (interested && !contains) {
insert(id);
} else if (!interested && contains) {
remove(id);
}
}
}
private void deleted(IntBag entities) {
int[] ids = entities.getData();
for (int i = 0, s = entities.size(); s > i; i++) {
int id = ids[i];
if (activeEntityIds.unsafeGet(id))
remove(id);
}
}
private void deletedAll(IntBag entities) {
int[] ids = entities.getData();
for (int i = 0, s = entities.size(); s > i; i++) {
int id = ids[i];
activeEntityIds.unsafeClear(id);
removedIds.unsafeSet(id);
}
}
/**
* Add listener interested in changes to the subscription.
*
* @param listener listener to add.
*/
public void addSubscriptionListener(SubscriptionListener listener) {
extra.listeners.add(listener);
}
/**
* Remove previously registered listener.
*
* @param listener listener to remove.
*/
public void removeSubscriptionListener(SubscriptionListener listener) {
extra.listeners.remove(listener);
}
@Override
public String toString() {
return "EntitySubscription[" + getAspectBuilder() + "]";
}
/**
* This interfaces reports entities inserted or
* removed when matched against their {@link com.artemis.EntitySubscription}
*
* Listeners are only triggered after a system finishes processing and the entity composition has changed.
*
* Replacing a component with another of the same type does not permanently change the composition and does not
* count as a composition change, neither does adding and immediately removing a component.
*/
public interface SubscriptionListener {
/**
* Called after entities match an {@link EntitySubscription}.
*
* Triggers right after a system finishes processing. Adding and immediately removing a component does not
* permanently change the composition and will prevent this method from being called.
*
* Not triggered for entities that have been destroyed immediately after being created (within a system).
*/
void inserted(IntBag entities);
/**
* Called after entities no longer match an EntitySubscription.
*
* Triggers right after a system finishes processing. Replacing a component with another of the same type
* within a system does not count as a composition change and will prevent this method from being called.
*
* Can trigger for entities that have been destroyed immediately after being created (within a system).
*
*
* Important note on accessing components:
* Using {@link ComponentMapper#get(int)} to retrieve a component is unsafe, unless:
* - You annotate the component with {@link DelayedComponentRemoval}.
* - {@link World#isAlwaysDelayComponentRemoval} is enabled to make accessing all components safe,
* for a small performance hit.
*
* {@link ComponentMapper#has(int)} always returns {@code false}, even for DelayedComponentRemoval components.
*/
void removed(IntBag entities);
}
public static class SubscriptionExtra {
final IntBag inserted = new IntBag();
final IntBag removed = new IntBag();
final Aspect aspect;
final Aspect.Builder aspectReflection;
final Bag listeners = new Bag();
public SubscriptionExtra(Aspect aspect, Aspect.Builder aspectReflection) {
this.aspect = aspect;
this.aspectReflection = aspectReflection;
}
void informEntityChanges() {
informListeners();
removed.setSize(0);
inserted.setSize(0);
}
private void informListeners() {
for (int i = 0, s = listeners.size(); s > i; i++) {
SubscriptionListener listener = listeners.get(i);
if (removed.size() > 0)
listener.removed(removed);
if (inserted.size() > 0)
listener.inserted(inserted);
}
}
}
}