Using Dynamic
The improbable.worker.Dynamic
class provides a way to execute arbitrary component-related logic in a
type-safe way even when the component is not known statically. Dynamic.forComponent
will invoke a
custom handler for a component with a specific ID; Dynamic.forEachComponent
will invoke a custom
handler for every known component. These handlers have enough information to register Dispatcher
callbacks, manipulate component updates and data, and so on.
This allows, for example, a custom View with any desired semantics to be implemented from scratch.
To do this, you would extend Dispatcher, adding data structures such as Map<EntityId, Entity> entities
and
Map<EntityId, HashSet<Integer>> authority
below to track the entities in the worker’s view.
You would then also need to define a class which implements the Dynamic.Handler
interface, providing type-safe
behaviour in the accept(final Metaclass metaclass)
method.
Dynamic
allows you to implement, for example, a custom View
with any desired semantics from scratch.
package docs;
import improbable.worker.*;
public class CustomView extends Dispatcher {
// Maintain a Map of the entities in this worker's view.
public final java.util.Map<EntityId, Entity> entities = new java.util.LinkedHashMap<>();
public final java.util.Map<EntityId, java.util.Map<Integer, Authority>> authority = new java.util.LinkedHashMap<>();
public <Metaclass extends ComponentMetaclass>
Authority getAuthority(Metaclass metaclass, EntityId entityId) {
java.util.Map<Integer, Authority> componentAuthorities = authority.get(entityId);
if (componentAuthorities == null) {
return Authority.NOT_AUTHORITATIVE;
}
Authority authority = componentAuthorities.get(metaclass.getComponentId());
return authority != null ? authority : Authority.NOT_AUTHORITATIVE;
}
// An implementation of Dynamic.Handler to provide type-safe behaviour for
// the components.
private class TrackComponent implements Dynamic.Handler {
// The Accept method defines the type-safe behaviour.
// Here, we are specifying callbacks to update our view's state
// when components are added or removed, when the worker's authority changes,
// or when we receive component updates.
@Override
public <Metaclass extends ComponentMetaclass<Data, Update>, Data, Update>
void accept(final Metaclass metaclass) {
onAddComponent(metaclass, op -> {
Entity entity = entities.get(op.entityId);
if (entity != null) {
entity.add(metaclass, op.data);
}
java.util.Map<Integer, Authority> entityAuthority = authority.get(op.entityId);
if (entityAuthority != null) {
entityAuthority.put(metaclass.getComponentId(), Authority.NOT_AUTHORITATIVE);
}
});
onRemoveComponent(metaclass, op -> {
Entity entity = entities.get(op.entityId);
if (entity != null) {
entity.remove(metaclass);
}
java.util.Map<Integer, Authority> entityAuthority = authority.get(op.entityId);
if (entityAuthority != null) {
entityAuthority.remove(metaclass.getComponentId());
}
});
onAuthorityChange(metaclass, op -> {
java.util.Map<Integer, Authority> entityAuthority = authority.get(op.entityId);
if (entityAuthority != null) {
entityAuthority.put(metaclass.getComponentId(), op.authority);
}
});
onComponentUpdate(metaclass, op -> {
Entity entity = entities.get(op.entityId);
if (entity != null) {
entity.update(metaclass, op.update);
}
});
}
}
public CustomView() {
// Update the entities Map to reflect when entities are added or removed.
onAddEntity(op -> {
entities.put(op.entityId, new Entity());
authority.put(op.entityId, new java.util.HashMap<>());
});
onRemoveEntity(op -> {
entities.remove(op.entityId);
authority.remove(op.entityId);
});
// Invoke the TrackComponent handler for every component.
Dynamic.forEachComponent(new TrackComponent());
}
}