diff options
| author | Elizabeth Hunt <me@liz.coffee> | 2026-02-15 10:08:30 -0800 |
|---|---|---|
| committer | Elizabeth Hunt <me@liz.coffee> | 2026-02-15 10:12:08 -0800 |
| commit | d6a294e14604aecd9046cbc19dc1837ca694fbb0 (patch) | |
| tree | 616e87e59f9ee95253acce05aa26405523a9acd7 /core/src/main/java/coffee/liz/ecs | |
| parent | 347564c592325007cb81dbdf1a07831c2ae003c3 (diff) | |
| download | the-abstraction-engine-java-d6a294e14604aecd9046cbc19dc1837ca694fbb0.tar.gz the-abstraction-engine-java-d6a294e14604aecd9046cbc19dc1837ca694fbb0.zip | |
feat: Adding ValueEmitter, LambdaEvaluatorSystem
Diffstat (limited to 'core/src/main/java/coffee/liz/ecs')
5 files changed, 134 insertions, 31 deletions
diff --git a/core/src/main/java/coffee/liz/ecs/DAGWorld.java b/core/src/main/java/coffee/liz/ecs/DAGWorld.java index 24d5adc..5d052ab 100644 --- a/core/src/main/java/coffee/liz/ecs/DAGWorld.java +++ b/core/src/main/java/coffee/liz/ecs/DAGWorld.java @@ -2,6 +2,7 @@ package coffee.liz.ecs; import coffee.liz.ecs.model.Component; import coffee.liz.ecs.model.Entity; +import coffee.liz.ecs.model.Query; import coffee.liz.ecs.model.System; import coffee.liz.ecs.model.World; @@ -10,10 +11,12 @@ import lombok.extern.log4j.Log4j2; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -42,9 +45,10 @@ public class DAGWorld<T> implements World<T> { /** Ordered list of systems for execution. */ private final List<System<T>> systemExecutionOrder; - public DAGWorld(final Map<Class<? extends System<T>>, System<T>> systems) { - this.systems = systems; - this.systemExecutionOrder = buildExecutionOrder(systems.values().stream().toList()); + @SafeVarargs + public DAGWorld(final System<T>... systems) { + this.systems = singletonClazzMap(systems); + this.systemExecutionOrder = buildExecutionOrder(Arrays.asList(systems)); log.debug("Executing in order: {}", systemExecutionOrder); } @@ -67,19 +71,13 @@ public class DAGWorld<T> implements World<T> { } @Override - public Set<Entity> query(final Collection<Class<? extends Component>> components) { - if (components.isEmpty()) { - return Set.copyOf(entities); - } - - final Class<? extends Component> firstType = components.iterator().next(); - final Set<Entity> candidates = componentCache.get(firstType); - if (candidates == null) { - return Collections.emptySet(); - } - - return candidates.stream().filter(entity -> components.stream().allMatch(entity::has)) - .collect(Collectors.toSet()); + public Set<Entity> resolve(final Query query) { + final Set<Class<? extends Component>> components = query.queryingComponents(); + return switch (query.filter()) { + case ALL_OF -> resolveAllOf(components); + case ANY_OF -> resolveAnyOf(components); + case NONE_OF -> resolveNoneOf(components); + }; } @Override @@ -103,15 +101,48 @@ public class DAGWorld<T> implements World<T> { componentType -> componentCache.computeIfAbsent(componentType, _ -> new HashSet<>()).add(entity))); } + private Set<Entity> resolveAllOf(final Set<Class<? extends Component>> components) { + if (components.isEmpty()) { + return Set.copyOf(entities); + } + + final Class<? extends Component> firstType = components.iterator().next(); + final Set<Entity> candidates = componentCache.get(firstType); + if (candidates == null) { + return Collections.emptySet(); + } + + return candidates.stream().filter(entity -> components.stream().allMatch(entity::has)) + .collect(Collectors.toSet()); + } + + private Set<Entity> resolveAnyOf(final Set<Class<? extends Component>> components) { + if (components.isEmpty()) { + return Collections.emptySet(); + } + + return entities.stream().filter(entity -> components.stream().anyMatch(entity::has)) + .collect(Collectors.toSet()); + } + + private Set<Entity> resolveNoneOf(final Set<Class<? extends Component>> components) { + if (components.isEmpty()) { + return Set.copyOf(entities); + } + + return entities.stream().filter(entity -> components.stream().noneMatch(entity::has)) + .collect(Collectors.toSet()); + } + private List<System<T>> buildExecutionOrder(final Collection<System<T>> systems) { if (systems.isEmpty()) { return Collections.emptyList(); } final Map<Class<?>, System<T>> systemMap = systems.stream() - .collect(Collectors.toMap(System::getClass, system -> system)); - final Map<Class<?>, Integer> inDegree = new HashMap<>(); - final Map<Class<?>, Set<Class<?>>> adjacencyList = new HashMap<>(); + .collect(Collectors.toMap(System::getClass, system -> system, (_, b) -> b, LinkedHashMap::new)); + final Map<Class<?>, Integer> inDegree = new LinkedHashMap<>(); + final Map<Class<?>, Set<Class<?>>> adjacencyList = new LinkedHashMap<>(); systems.forEach(system -> { final Class<?> systemClass = system.getClass(); @@ -155,11 +186,23 @@ public class DAGWorld<T> implements World<T> { } @Override - public void close() throws Exception { + public void dispose() { for (final System<T> system : systemExecutionOrder) { - system.close(); + system.dispose(); } componentCache.clear(); entities.clear(); } + + @SuppressWarnings("unchecked") + private static <T> Map<Class<? extends T>, T> singletonClazzMap(final T... singletons) { + final boolean areSingletons = Arrays.stream(singletons).map(t -> (Class<? extends System<T>>) t.getClass()) + .distinct().count() == singletons.length; + if (!areSingletons) { + throw new IllegalArgumentException("Only one instance may be used per clazz"); + } + + return Arrays.stream(singletons).map(t -> Map.entry((Class<? extends T>) t.getClass(), t)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } } diff --git a/core/src/main/java/coffee/liz/ecs/model/Query.java b/core/src/main/java/coffee/liz/ecs/model/Query.java new file mode 100644 index 0000000..679ea49 --- /dev/null +++ b/core/src/main/java/coffee/liz/ecs/model/Query.java @@ -0,0 +1,30 @@ +package coffee.liz.ecs.model; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +public record Query(Set<Class<? extends Component>> queryingComponents, QueryFilter filter) { + public Query { + queryingComponents = Set.copyOf(queryingComponents); + } + + @SafeVarargs + public static Query allOf(final Class<? extends Component>... components) { + return new Query(Arrays.stream(components).collect(Collectors.toSet()), QueryFilter.ALL_OF); + } + + @SafeVarargs + public static Query anyOf(final Class<? extends Component>... components) { + return new Query(Arrays.stream(components).collect(Collectors.toSet()), QueryFilter.ANY_OF); + } + + @SafeVarargs + public static Query noneOf(final Class<? extends Component>... components) { + return new Query(Arrays.stream(components).collect(Collectors.toSet()), QueryFilter.NONE_OF); + } + + public enum QueryFilter { + ALL_OF, ANY_OF, NONE_OF + } +} diff --git a/core/src/main/java/coffee/liz/ecs/model/QueryBuilder.java b/core/src/main/java/coffee/liz/ecs/model/QueryBuilder.java new file mode 100644 index 0000000..eba5021 --- /dev/null +++ b/core/src/main/java/coffee/liz/ecs/model/QueryBuilder.java @@ -0,0 +1,25 @@ +package coffee.liz.ecs.model; + +import lombok.RequiredArgsConstructor; + +import java.util.Set; + +@RequiredArgsConstructor +public class QueryBuilder<T> { + private final World<T> world; + + @SafeVarargs + public final Set<Entity> allOf(final Class<? extends Component>... components) { + return world.resolve(Query.allOf(components)); + } + + @SafeVarargs + public final Set<Entity> anyOf(final Class<? extends Component>... components) { + return world.resolve(Query.anyOf(components)); + } + + @SafeVarargs + public final Set<Entity> noneOf(final Class<? extends Component>... components) { + return world.resolve(Query.noneOf(components)); + } +} diff --git a/core/src/main/java/coffee/liz/ecs/model/System.java b/core/src/main/java/coffee/liz/ecs/model/System.java index c4b0bc0..8138a0b 100644 --- a/core/src/main/java/coffee/liz/ecs/model/System.java +++ b/core/src/main/java/coffee/liz/ecs/model/System.java @@ -9,7 +9,7 @@ import java.util.Collection; * @param <T> * is the state of the stuff outside the {@link World}. */ -public interface System<T> extends AutoCloseable { +public interface System<T> { /** * {@link System} clazzes that must run before this system. * @@ -27,7 +27,6 @@ public interface System<T> extends AutoCloseable { */ void update(final World<T> world, final T state, final Duration dt); - @Override - default void close() { + default void dispose() { } } diff --git a/core/src/main/java/coffee/liz/ecs/model/World.java b/core/src/main/java/coffee/liz/ecs/model/World.java index c59a0d7..668c3d5 100644 --- a/core/src/main/java/coffee/liz/ecs/model/World.java +++ b/core/src/main/java/coffee/liz/ecs/model/World.java @@ -1,16 +1,15 @@ package coffee.liz.ecs.model; import java.time.Duration; -import java.util.Collection; import java.util.Set; /** - * The state of the world. + * The game world. * * @param <T> * is the state of the stuff outside the world. */ -public interface World<T> extends AutoCloseable { +public interface World<T> { /** * Create unique {@link Entity} in the {@link World}. * @@ -29,11 +28,15 @@ public interface World<T> extends AutoCloseable { /** * Get entities with {@link Component}s. * - * @param components - * to query for. - * @return All entities with all {@param components}. + * @param query + * to query. + * @return All entities satisfying {@param query}. */ - Set<Entity> query(final Collection<Class<? extends Component>> components); + Set<Entity> resolve(final Query query); + + default QueryBuilder<T> queryable() { + return new QueryBuilder<>(this); + } /** * Integrate the {@link World}. @@ -55,4 +58,7 @@ public interface World<T> extends AutoCloseable { * @return {@link System} instance of {@param system}. */ <S extends System<T>> S getSystem(final Class<S> system); + + default void dispose() { + } } |
