diff options
| author | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-02-28 14:08:49 -0800 |
|---|---|---|
| committer | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-02-28 14:08:49 -0800 |
| commit | 8412efda977c1c76885eae1d0b4a721cf71162f2 (patch) | |
| tree | 4ff20bc346fd24aeb5881ea06855d7bea5f5d162 | |
| parent | 87c8a1e15e399d29f42b41a4ccb66a84c5f6bb9a (diff) | |
| download | dyl-8412efda977c1c76885eae1d0b4a721cf71162f2.tar.gz dyl-8412efda977c1c76885eae1d0b4a721cf71162f2.zip | |
Upgrading JDK and adding Observable interface
| -rw-r--r-- | build.gradle | 3 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/dyl/components/StressComponent.java | 16 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/dyl/systems/StressSystem.java | 31 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java | 6 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/ecs/ComponentCache.java | 124 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/ecs/DAGWorld.java | 63 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/ecs/events/ComponentAdded.java | 16 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/ecs/events/ComponentRemoved.java | 21 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/ecs/events/EntityEvent.java | 13 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/ecs/events/Hook.java | 13 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/ecs/events/Observable.java | 34 | ||||
| -rw-r--r-- | core/src/main/java/coffee/liz/ecs/model/Entity.java | 41 | ||||
| -rw-r--r-- | core/src/test/java/coffee/liz/ecs/DAGWorldTest.java | 20 | ||||
| -rw-r--r-- | core/src/test/java/coffee/liz/ecs/model/EntityTest.java | 30 | ||||
| -rw-r--r-- | gradle/gradle-daemon-jvm.properties | 22 | ||||
| -rw-r--r-- | lwjgl3/build.gradle | 14 |
16 files changed, 404 insertions, 63 deletions
diff --git a/build.gradle b/build.gradle index 817f7a3..79053e4 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,8 @@ allprojects { configure(subprojects) { apply plugin: 'java-library' - java.sourceCompatibility = 17 + java.sourceCompatibility = JavaVersion.VERSION_24 + java.targetCompatibility = JavaVersion.VERSION_24 dependencies { compileOnly 'org.projectlombok:lombok:1.18.42' diff --git a/core/src/main/java/coffee/liz/dyl/components/StressComponent.java b/core/src/main/java/coffee/liz/dyl/components/StressComponent.java new file mode 100644 index 0000000..f22dfaa --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/StressComponent.java @@ -0,0 +1,16 @@ +package coffee.liz.dyl.components; + +import coffee.liz.ecs.model.Component; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; + +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@RequiredArgsConstructor +public class StressComponent implements Component { + private final String componentData; + public StressComponent() { + componentData = IntStream.range(0, 10).mapToObj(Integer::toString).collect(Collectors.joining()); + } +} diff --git a/core/src/main/java/coffee/liz/dyl/systems/StressSystem.java b/core/src/main/java/coffee/liz/dyl/systems/StressSystem.java new file mode 100644 index 0000000..850a9f3 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/systems/StressSystem.java @@ -0,0 +1,31 @@ +package coffee.liz.dyl.systems; + +import coffee.liz.dyl.FrameState; +import coffee.liz.dyl.components.StressComponent; +import coffee.liz.dyl.components.Velocity; +import coffee.liz.ecs.math.Vec2f; +import coffee.liz.ecs.model.System; +import coffee.liz.ecs.model.World; + +import java.util.Collection; +import java.util.List; + +public class StressSystem implements System<FrameState> { + + @Override + public Collection<Class<? extends System<FrameState>>> getDependencies() { + return List.of(); + } + + @Override + public void update(World<FrameState> world, FrameState state, float deltaSeconds) { + world.queryable().allOf().forEach(e -> { + if (Math.random() > 0.5) { + e.add(new StressComponent()); + e.add(new Velocity(new Vec2f(0.5f - (float) Math.random(), 0.5f - (float) Math.random()))); + } else if (e.has(StressComponent.class)){ + e.remove(StressComponent.class); + } + }); + } +} diff --git a/core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java b/core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java index c3e1c2d..475694e 100644 --- a/core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java +++ b/core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java @@ -7,6 +7,7 @@ import coffee.liz.dyl.entities.PlayerFactory; import coffee.liz.dyl.systems.InputSystem; import coffee.liz.dyl.systems.IntegrationSystem; import coffee.liz.dyl.systems.RenderSystem; +import coffee.liz.dyl.systems.StressSystem; import coffee.liz.ecs.DAGWorld; import coffee.liz.ecs.math.Vec2f; import coffee.liz.ecs.math.Vec2i; @@ -16,9 +17,10 @@ public class DylGameWorld extends DAGWorld<FrameState> { super( new InputSystem(), new IntegrationSystem(), - new RenderSystem(game.getBatch(), game.getViewport()) + new RenderSystem(game.getBatch(), game.getViewport()), + new StressSystem() ); - for (int i = 0; i < 8000; i++) { + for (int i = 0; i < 16_000; i++) { PlayerFactory.addTo(this) .add(new BoundingBox(new Vec2f(i / 200f, i/ 200f), new Vec2i(1, 1), 1)); } diff --git a/core/src/main/java/coffee/liz/ecs/ComponentCache.java b/core/src/main/java/coffee/liz/ecs/ComponentCache.java new file mode 100644 index 0000000..70487d6 --- /dev/null +++ b/core/src/main/java/coffee/liz/ecs/ComponentCache.java @@ -0,0 +1,124 @@ +package coffee.liz.ecs; + +import coffee.liz.ecs.events.ComponentAdded; +import coffee.liz.ecs.events.ComponentRemoved; +import coffee.liz.ecs.events.EntityEvent; +import coffee.liz.ecs.model.Component; +import coffee.liz.ecs.model.Entity; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** Incremental cache for component-to-entity lookups. */ +public final class ComponentCache { + private final Map<Class<? extends Component>, Set<Entity>> entitiesByComponent = new HashMap<>(); + + /** + * Index all current components on a newly tracked entity. + * + * @param entity + * entity to index + */ + public void addEntity(final Entity entity) { + entity.componentTypes().forEach(componentType -> addComponentEntry(componentType, entity)); + } + + /** + * Remove all current components for a no-longer-tracked entity. + * + * @param entity + * entity to remove from the cache + */ + public void removeEntity(final Entity entity) { + entity.componentTypes().forEach(componentType -> removeComponentEntry(componentType, entity)); + } + + /** + * Apply a mutation event to the cache. + * + * @param event + * mutation event emitted by an entity + */ + public void onEntityEvent(final EntityEvent event) { + switch (event) { + case ComponentAdded componentAdded: + addComponentEntry(componentAdded.getComponentClazz(), componentAdded.getEntity()); + break; + case ComponentRemoved componentRemoved: + removeComponentEntry(componentRemoved.getComponentClazz(), componentRemoved.getEntity()); + break; + default: + break; + } + } + + /** + * Get all entities that contain a component type. + * + * @param componentType + * component class to query + * @return entities containing that component + */ + public Set<Entity> entitiesWith(final Class<? extends Component> componentType) { + return entitiesByComponent.getOrDefault(componentType, Collections.emptySet()); + } + + /** + * Resolve entities that contain every queried component type. + * + * @param componentTypes + * required component classes + * @return entities that have all queried component types + */ + public Set<Entity> entitiesMatchingAllOf(final Collection<Class<? extends Component>> componentTypes) { + if (componentTypes.isEmpty()) { + return Collections.emptySet(); + } + + Set<Entity> smallestBucket = null; + for (final Class<? extends Component> componentType : componentTypes) { + final Set<Entity> entities = entitiesWith(componentType); + if (entities.isEmpty()) { + return Collections.emptySet(); + } + if (smallestBucket == null || entities.size() < smallestBucket.size()) { + smallestBucket = entities; + } + } + + final Set<Entity> matches = new HashSet<>(); + if (smallestBucket == null) { + return matches; + } + + for (final Entity entity : smallestBucket) { + if (entity.hasAll(componentTypes)) { + matches.add(entity); + } + } + return matches; + } + + /** + * Remove all cached component indexes. + */ + public void clear() { + entitiesByComponent.clear(); + } + + private void addComponentEntry(final Class<? extends Component> componentType, final Entity entity) { + entitiesByComponent.computeIfAbsent(componentType, _unused -> new HashSet<>()).add(entity); + } + + private void removeComponentEntry(final Class<? extends Component> componentType, final Entity entity) { + final Set<Entity> entities = entitiesByComponent.get(componentType); + entities.remove(entity); + if (entities.isEmpty()) { + entitiesByComponent.remove(componentType); + } + } +} diff --git a/core/src/main/java/coffee/liz/ecs/DAGWorld.java b/core/src/main/java/coffee/liz/ecs/DAGWorld.java index 1075e5e..a6a11db 100644 --- a/core/src/main/java/coffee/liz/ecs/DAGWorld.java +++ b/core/src/main/java/coffee/liz/ecs/DAGWorld.java @@ -1,5 +1,7 @@ package coffee.liz.ecs; +import coffee.liz.ecs.events.EntityEvent; +import coffee.liz.ecs.events.Hook; import coffee.liz.ecs.model.Component; import coffee.liz.ecs.model.Entity; import coffee.liz.ecs.model.Query; @@ -13,7 +15,6 @@ 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; @@ -31,9 +32,11 @@ public class DAGWorld<T> implements World<T> { /** All entities in the world. */ protected final Set<Entity> entities = Collections.synchronizedSet(new HashSet<>()); - /** Cache mapping component types to entities having that component. */ - private final Map<Class<? extends Component>, Set<Entity>> componentCache = Collections - .synchronizedMap(new HashMap<>()); + /** Incremental cache mapping component types to entities having that component. */ + private final ComponentCache componentCache = new ComponentCache(); + + /** Shared world listener to keep cache synchronized with entity component changes. */ + private final Hook<EntityEvent> cacheUpdateHook = componentCache::onEntityEvent; /** Deterministic ID's for spawned entities. */ private final AtomicInteger nextEntityId = new AtomicInteger(0); @@ -48,24 +51,22 @@ public class DAGWorld<T> implements World<T> { public DAGWorld(final System<T>... systems) { this.systems = singletonClazzMap(systems); this.systemExecutionOrder = buildExecutionOrder(Arrays.asList(systems)); - log.debug("Executing in order: {}", systemExecutionOrder); + log.info("Executing in order: {}", systemExecutionOrder); } @Override public Entity createEntity() { final Entity entity = Entity.builder().id(nextEntityId.incrementAndGet()).build(); + entity.subscribe(cacheUpdateHook); entities.add(entity); + componentCache.addEntity(entity); return entity; } @Override public void removeEntity(final Entity entity) { - entity.getComponentMap().keySet().forEach(componentType -> { - final Set<Entity> cachedEntities = componentCache.get(componentType); - if (cachedEntities != null) { - cachedEntities.remove(entity); - } - }); + entity.unsubscribe(cacheUpdateHook); + componentCache.removeEntity(entity); entities.remove(entity); } @@ -81,11 +82,7 @@ public class DAGWorld<T> implements World<T> { @Override public void update(final T state, final float deltaSeconds) { - systemExecutionOrder.forEach(system -> { - refreshComponentCache(); - system.update(this, state, deltaSeconds); - }); - refreshComponentCache(); + systemExecutionOrder.forEach(system -> system.update(this, state, deltaSeconds)); } @SuppressWarnings("unchecked") @@ -94,25 +91,11 @@ public class DAGWorld<T> implements World<T> { return (S) systems.get(system); } - private void refreshComponentCache() { - componentCache.clear(); - entities.forEach(entity -> entity.getComponentMap().keySet().forEach( - componentType -> componentCache.computeIfAbsent(componentType, _comp -> 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()); + return componentCache.entitiesMatchingAllOf(components); } private Set<Entity> resolveAnyOf(final Set<Class<? extends Component>> components) { @@ -120,8 +103,9 @@ public class DAGWorld<T> implements World<T> { return Collections.emptySet(); } - return entities.stream().filter(entity -> components.stream().anyMatch(entity::has)) - .collect(Collectors.toSet()); + final Set<Entity> matches = new HashSet<>(); + components.forEach(componentType -> matches.addAll(componentCache.entitiesWith(componentType))); + return matches; } private Set<Entity> resolveNoneOf(final Set<Class<? extends Component>> components) { @@ -129,8 +113,16 @@ public class DAGWorld<T> implements World<T> { return Set.copyOf(entities); } - return entities.stream().filter(entity -> components.stream().noneMatch(entity::has)) - .collect(Collectors.toSet()); + final Set<Entity> excluded = new HashSet<>(); + components.forEach(componentType -> excluded.addAll(componentCache.entitiesWith(componentType))); + + final Set<Entity> result = new HashSet<>(); + entities.forEach(entity -> { + if (!excluded.contains(entity)) { + result.add(entity); + } + }); + return result; } private List<System<T>> buildExecutionOrder(final Collection<System<T>> systems) { @@ -189,6 +181,7 @@ public class DAGWorld<T> implements World<T> { for (final System<T> system : systemExecutionOrder) { system.dispose(); } + entities.forEach(entity -> entity.unsubscribe(cacheUpdateHook)); componentCache.clear(); entities.clear(); } diff --git a/core/src/main/java/coffee/liz/ecs/events/ComponentAdded.java b/core/src/main/java/coffee/liz/ecs/events/ComponentAdded.java new file mode 100644 index 0000000..6837bfc --- /dev/null +++ b/core/src/main/java/coffee/liz/ecs/events/ComponentAdded.java @@ -0,0 +1,16 @@ +package coffee.liz.ecs.events; + +import coffee.liz.ecs.model.Component; +import coffee.liz.ecs.model.Entity; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Emitted when an entity receives a component. + */ +@RequiredArgsConstructor +@Getter +public class ComponentAdded implements EntityEvent { + private final Entity entity; + private final Class<? extends Component> componentClazz; +} diff --git a/core/src/main/java/coffee/liz/ecs/events/ComponentRemoved.java b/core/src/main/java/coffee/liz/ecs/events/ComponentRemoved.java new file mode 100644 index 0000000..25047b6 --- /dev/null +++ b/core/src/main/java/coffee/liz/ecs/events/ComponentRemoved.java @@ -0,0 +1,21 @@ +package coffee.liz.ecs.events; + +import coffee.liz.ecs.model.Component; +import coffee.liz.ecs.model.Entity; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Emitted when an entity loses a component. + * + * @param entity + * entity that lost a component + * @param componentClazz + * class key of the component that was removed + */ +@RequiredArgsConstructor +@Getter +public class ComponentRemoved implements EntityEvent { + private final Entity entity; + private final Class<? extends Component> componentClazz; +} diff --git a/core/src/main/java/coffee/liz/ecs/events/EntityEvent.java b/core/src/main/java/coffee/liz/ecs/events/EntityEvent.java new file mode 100644 index 0000000..cae20e6 --- /dev/null +++ b/core/src/main/java/coffee/liz/ecs/events/EntityEvent.java @@ -0,0 +1,13 @@ +package coffee.liz.ecs.events; + +import coffee.liz.ecs.model.Entity; + +/** Marker interface for events emitted by {@link Entity}. */ +public interface EntityEvent { + /** + * Get the entity that emitted this event. + * + * @return source {@link Entity} + */ + Entity getEntity(); +} diff --git a/core/src/main/java/coffee/liz/ecs/events/Hook.java b/core/src/main/java/coffee/liz/ecs/events/Hook.java new file mode 100644 index 0000000..8f91aad --- /dev/null +++ b/core/src/main/java/coffee/liz/ecs/events/Hook.java @@ -0,0 +1,13 @@ +package coffee.liz.ecs.events; + +import java.util.function.Consumer; + +/** + * Event callback used by {@link Observable}. + * + * @param <E> + * consumed event type + */ +@FunctionalInterface +public interface Hook<E> extends Consumer<E> { +} diff --git a/core/src/main/java/coffee/liz/ecs/events/Observable.java b/core/src/main/java/coffee/liz/ecs/events/Observable.java new file mode 100644 index 0000000..d849407 --- /dev/null +++ b/core/src/main/java/coffee/liz/ecs/events/Observable.java @@ -0,0 +1,34 @@ +package coffee.liz.ecs.events; + +/** + * Minimal observable contract for event emission. + * + * @param <E> + * emitted event type + */ +public interface Observable<E> { + /** + * Register a hook to receive future events. + * + * @param hook + * callback to invoke for each emitted event + * @return the registered hook + */ + Hook<E> subscribe(Hook<E> hook); + + /** + * Remove a previously registered hook. + * + * @param hook + * callback to remove + */ + void unsubscribe(Hook<E> hook); + + /** + * Publish an event to all currently subscribed hooks. + * + * @param event + * event to publish + */ + void emit(E event); +} diff --git a/core/src/main/java/coffee/liz/ecs/model/Entity.java b/core/src/main/java/coffee/liz/ecs/model/Entity.java index 7dab667..4984d5d 100644 --- a/core/src/main/java/coffee/liz/ecs/model/Entity.java +++ b/core/src/main/java/coffee/liz/ecs/model/Entity.java @@ -1,14 +1,22 @@ package coffee.liz.ecs.model; +import coffee.liz.ecs.events.ComponentAdded; +import coffee.liz.ecs.events.ComponentRemoved; +import coffee.liz.ecs.events.EntityEvent; +import coffee.liz.ecs.events.Hook; +import coffee.liz.ecs.events.Observable; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; @@ -17,7 +25,7 @@ import java.util.Set; @RequiredArgsConstructor @AllArgsConstructor @Data -public class Entity { +public class Entity implements Observable<EntityEvent> { /** Unique id. */ private final int id; @@ -25,6 +33,10 @@ public class Entity { @Builder.Default private Map<Class<? extends Component>, Component> componentMap = Collections.synchronizedMap(new HashMap<>()); + @Builder.Default + /** Event subscribers for entity mutation events. */ + private transient Set<Hook<EntityEvent>> hooks = Collections.newSetFromMap(new IdentityHashMap<>()); + /** * Check if entity has component type. * @@ -76,7 +88,9 @@ public class Entity { * @return this {@link Entity} for chaining */ public <C extends Component> Entity add(final C component) { - componentMap.put(component.getKey(), component); + final Class<? extends Component> componentType = component.getKey(); + componentMap.put(componentType, component); + emit(new ComponentAdded(this, componentType)); return this; } @@ -90,7 +104,9 @@ public class Entity { * @return this {@link Entity} for chaining */ public <C extends Component> Entity remove(final Class<C> componentType) { - componentMap.remove(componentType); + if (componentMap.remove(componentType) != null) { + emit(new ComponentRemoved(this, componentType)); + } return this; } @@ -102,4 +118,23 @@ public class Entity { public Set<Class<? extends Component>> componentTypes() { return componentMap.keySet(); } + + /** Subscribe to component mutation events emitted by this entity. */ + @Override + public Hook<EntityEvent> subscribe(final Hook<EntityEvent> hook) { + hooks.add(hook); + return hook; + } + + /** Remove a previously subscribed mutation hook. */ + @Override + public void unsubscribe(final Hook<EntityEvent> hook) { + hooks.remove(hook); + } + + /** Emit a mutation event to all current subscribers. */ + @Override + public void emit(final EntityEvent event) { + new ArrayList<>(hooks).forEach(hook -> hook.accept(event)); + } } diff --git a/core/src/test/java/coffee/liz/ecs/DAGWorldTest.java b/core/src/test/java/coffee/liz/ecs/DAGWorldTest.java index cf6cdad..4825e36 100644 --- a/core/src/test/java/coffee/liz/ecs/DAGWorldTest.java +++ b/core/src/test/java/coffee/liz/ecs/DAGWorldTest.java @@ -68,19 +68,31 @@ public class DAGWorldTest { } @Test - public void updateRefreshesComponentCacheAfterEntityMutations() { + public void cacheTracksComponentMutationsViaEntityEvents() { final DAGWorld<String> world = new DAGWorld<>(); final Entity subject = world.createEntity(); - world.update("state", 0); assertTrue(world.resolve(Query.allOf(PositionComponent.class)).isEmpty()); subject.add(new PositionComponent()); - world.update("state", 0); assertEquals(1, world.resolve(Query.allOf(PositionComponent.class)).size()); subject.remove(PositionComponent.class); - world.update("state", 0); + assertTrue(world.resolve(Query.allOf(PositionComponent.class)).isEmpty()); + } + + @Test + public void removedEntityNoLongerMutatesWorldCache() { + final DAGWorld<String> world = new DAGWorld<>(); + final Entity subject = world.createEntity(); + + subject.add(new PositionComponent()); + assertEquals(Set.of(subject), world.resolve(Query.allOf(PositionComponent.class))); + + world.removeEntity(subject); + subject.remove(PositionComponent.class); + subject.add(new PositionComponent()); + assertTrue(world.resolve(Query.allOf(PositionComponent.class)).isEmpty()); } diff --git a/core/src/test/java/coffee/liz/ecs/model/EntityTest.java b/core/src/test/java/coffee/liz/ecs/model/EntityTest.java index a8fd1e3..bb17296 100644 --- a/core/src/test/java/coffee/liz/ecs/model/EntityTest.java +++ b/core/src/test/java/coffee/liz/ecs/model/EntityTest.java @@ -6,6 +6,11 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import coffee.liz.ecs.events.ComponentAdded; +import coffee.liz.ecs.events.ComponentRemoved; +import coffee.liz.ecs.events.EntityEvent; +import coffee.liz.ecs.events.Hook; + import lombok.RequiredArgsConstructor; import org.junit.jupiter.api.Test; @@ -15,6 +20,7 @@ import org.junit.jupiter.params.provider.MethodSource; import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; final class EntityTest { @@ -75,6 +81,30 @@ final class EntityTest { assertTrue(entity.componentTypes().isEmpty()); } + @Test + public void subscribeReceivesEmittedEvents() { + final Entity entity = Entity.builder().id(51).build(); + final AtomicInteger addedCount = new AtomicInteger(0); + final AtomicInteger removedCount = new AtomicInteger(0); + + final Hook<EntityEvent> hook = entity.subscribe(event -> { + if (event instanceof ComponentAdded) { + addedCount.incrementAndGet(); + } + if (event instanceof ComponentRemoved) { + removedCount.incrementAndGet(); + } + }); + + entity.add(new AlphaComponent("a")); + entity.remove(AlphaComponent.class); + + assertEquals(1, addedCount.get()); + assertEquals(1, removedCount.get()); + + entity.unsubscribe(hook); + } + private record AlphaComponent(String name) implements Component { } diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties index e5d86a4..1ad40d6 100644 --- a/gradle/gradle-daemon-jvm.properties +++ b/gradle/gradle-daemon-jvm.properties @@ -1,12 +1,12 @@ #This file is generated by updateDaemonJvm -toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect -toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/54001d0b636ad500b432d20ef3d580d0/redirect -toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect -toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/54001d0b636ad500b432d20ef3d580d0/redirect -toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29c55e6bad8a0049163f0184625cecd9/redirect -toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/3ac7a5361c25c0b23d933f44bdb0abd9/redirect -toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect -toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/54001d0b636ad500b432d20ef3d580d0/redirect -toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/dd5b582862cacd4b8e0d82037f92a53f/redirect -toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/16008c489780dfb402c44316e612a16c/redirect -toolchainVersion=17 +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/6d0adbce30460017fe61d2993dfa663e/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/ce3ff383a4a3e769ac7d9ca5903aa698/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/44715d7d372da8362a7c7e78c011e897/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/50f16d2dc2bb80a421afc1af38fc92e3/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/4f4ebe4f162f6deb29540c4ebe629d79/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/79d5995ef1c3e4df39a3b2f545cada5e/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/6d0adbce30460017fe61d2993dfa663e/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/50f16d2dc2bb80a421afc1af38fc92e3/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/826893cbcf0f86d8eb0975e2fb0788f7/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/747369971f89e1f6bc182b737d168306/redirect +toolchainVersion=24 diff --git a/lwjgl3/build.gradle b/lwjgl3/build.gradle index 26356c4..76f521b 100644 --- a/lwjgl3/build.gradle +++ b/lwjgl3/build.gradle @@ -22,10 +22,10 @@ sourceSets.main.resources.srcDirs += [ rootProject.file('assets').path ] application.mainClass = 'coffee.liz.dyl.lwjgl3.Lwjgl3Launcher' application.applicationName = appName eclipse.project.name = appName + '-lwjgl3' -java.sourceCompatibility = 17 -java.targetCompatibility = 17 +java.sourceCompatibility = JavaVersion.VERSION_24 +java.targetCompatibility = JavaVersion.VERSION_24 if (JavaVersion.current().isJava9Compatible()) { - compileJava.options.release.set(17) + compileJava.options.release.set(24) } dependencies { @@ -127,12 +127,12 @@ construo { targets.configure { register("linuxX64", Target.Linux) { architecture.set(Target.Architecture.X86_64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz") + jdkUrl.set("https://api.adoptium.net/v3/binary/latest/24/ga/linux/x64/jdk/hotspot/normal/eclipse") // Linux does not currently have a way to set the icon on the executable } register("macM1", Target.MacOs) { architecture.set(Target.Architecture.AARCH64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.15_6.tar.gz") + jdkUrl.set("https://api.adoptium.net/v3/binary/latest/24/ga/mac/aarch64/jdk/hotspot/normal/eclipse") // macOS needs an identifier identifier.set("coffee.liz.dyl." + appName) // Optional: icon for macOS, as an ICNS file @@ -140,7 +140,7 @@ construo { } register("macX64", Target.MacOs) { architecture.set(Target.Architecture.X86_64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_mac_hotspot_17.0.15_6.tar.gz") + jdkUrl.set("https://api.adoptium.net/v3/binary/latest/24/ga/mac/x64/jdk/hotspot/normal/eclipse") // macOS needs an identifier identifier.set("coffee.liz.dyl." + appName) // Optional: icon for macOS, as an ICNS file @@ -150,7 +150,7 @@ construo { architecture.set(Target.Architecture.X86_64) // Optional: icon for Windows, as a PNG icon.set(project.file("icons/logo.png")) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_windows_hotspot_17.0.15_6.zip") + jdkUrl.set("https://api.adoptium.net/v3/binary/latest/24/ga/windows/x64/jdk/hotspot/normal/eclipse") // Uncomment the next line to show a console when the game runs, to print messages. //useConsole.set(true) } |
