aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/java/coffee/liz/ecs
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2026-02-15 10:08:30 -0800
committerElizabeth Hunt <me@liz.coffee>2026-02-15 10:12:08 -0800
commitd6a294e14604aecd9046cbc19dc1837ca694fbb0 (patch)
tree616e87e59f9ee95253acce05aa26405523a9acd7 /core/src/main/java/coffee/liz/ecs
parent347564c592325007cb81dbdf1a07831c2ae003c3 (diff)
downloadthe-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')
-rw-r--r--core/src/main/java/coffee/liz/ecs/DAGWorld.java85
-rw-r--r--core/src/main/java/coffee/liz/ecs/model/Query.java30
-rw-r--r--core/src/main/java/coffee/liz/ecs/model/QueryBuilder.java25
-rw-r--r--core/src/main/java/coffee/liz/ecs/model/System.java5
-rw-r--r--core/src/main/java/coffee/liz/ecs/model/World.java20
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() {
+ }
}