Git Product home page Git Product logo

dominion-ecs-java's Introduction

|) () |\/| | |\| | () |\|

Java CI with Maven CodeQL

Dominion is an Entity Component System library for Java.

Entity Component System (ECS) architecture promotes data-oriented programming. It’s all about data (components) and first-class functions (systems) that operate on data.

This means that, unlike OOP, data and operations are not encapsulated together in objects, which are called entities in ECS.

Entities model the business objects of the user application, and the entity promotes "composition over inheritance" by grouping a dynamic list of components to define its specific features.

Systems usually operate on components sequentially and can be very fast if data are stored in cache-friendly ways. Systems are decoupled from each other and each system knows only about the data it operates on. This strongly promotes high concurrency, running systems in parallel whenever they can independently operate on the data.

ECS architecture is particularly suitable (but not limited to) if you have to manage many objects in your application. In addition, application code tends to be more reusable and easier to extend with new functionality thanks to the components' composition and subsequent addition of new systems.

Dominion Features

  • πŸš€ FAST > Dominion is not only an insanely fast ECS implemented in Java, it can also be in the same league as ECS for C, C++, and Rust - see benchmarks .
  • πŸš€πŸš€ FASTER > Dominion is on average quite faster than other ECS implemented in Java. Check out this performance comparison .
  • 🀏 TINY > Just a high-performance and high-concurrency Java library with a minimal footprint and no dependencies. So you can easily integrate the Dominion core library into your game engine or framework or use it directly for your game or application without warring about the dependency hell.
  • β˜•οΈ SIMPLE > Dominion promotes a clean, minimal and self-explanatory API that is simple by design. A few readme pages will provide complete usage documentation.
  • πŸ’ͺ with SUPPORT > Join the Discord! The server will support users and announce the availability of the new version.

Archetype Concept

Dominion implements the so-called Archetype concept through the DataComposition class, an aggregation of component types of which an entity can be made of.

In an ECS, the Archetype is a way to organize and manage entities based on their components. It provides a structured approach for efficiently storing and querying entities with similar component compositions.

In an ECS, entities are composed of various components that define their behavior and characteristics. The Archetype groups entities into archetypes based on their shared component types. An archetype represents a specific combination of component types that entities possess.

The purpose of using archetypes is to optimize entity storage and system processing. Entities within the same archetype have the same layout of components, which allows for data-oriented design and improves memory locality. This arrangement enhances cache coherency, enables more efficient processing of system operations, and is primarily handled in Dominion through the ChunkedPool.Tenant class.

Archetypes also facilitate efficient querying and iteration over entities that match specific component combinations. By organizing entities based on their archetypes, systems can quickly identify and process only the relevant entities that contain the required component types, reducing unnecessary overhead.

When a new entity is created or modified, the ECS manager checks the archetype it belongs to. If an existing archetype matches the component composition, the entity is added to that archetype. Otherwise, a new archetype is created for the entity's unique component combination.

By leveraging the Archetype, Dominion implementations can achieve high performance and scalability by optimizing memory usage, facilitating cache-friendly access patterns, and enabling efficient processing of entities with similar component compositions.

Overall, the Archetype in an Entity Component System provides an effective means of organizing, storing, and processing entities with shared component types, leading to improved performance and flexibility in game development and other related domains.

Struct of Arrays

Dominion implements a specific Struct of Arrays (SoA) layout through the ChunkedPool.LinkedChunk class.

In an ECS, the SoA layout refers to a data organization approach where the components of entities are stored in separate arrays based on their data types. This is in contrast to the traditional approach of storing entities as Arrays of Structs (AoS) where all components of an entity are grouped together in a single struct.

The SoA layout is designed to improve data locality and cache efficiency, which can lead to significant performance benefits in certain scenarios. By storing components of the same data type in contiguous arrays, the SoA layout allows for better memory access patterns, reducing cache misses and improving CPU cache utilization.

For example, let's consider an ECS with three component types: Position, Velocity, and Renderable. In an SoA layout, all Position components would be stored in a separate array, all Velocity components in another array, and so on. Each array would contain the respective component data for the entities.

By leveraging the SoA layout, which stores components of the same data type in contiguous arrays, data can be easily aligned for SIMD operations. This alignment allows SIMD instructions to operate on multiple data elements in parallel, providing significant performance benefits.

Overall, the SoA layout in Dominion provides a way to optimize memory access patterns and improve performance by storing components in separate arrays based on their data types, allowing for efficient batch processing of components by systems.

Quick Start

In your local environment you must have already installed a Java 17 (or newer) and Maven.

Add the following dependency declaration in your project pom.xml:

<dependency>
    <groupId>dev.dominion.ecs</groupId>
    <artifactId>dominion-ecs-engine</artifactId>
    <version>0.9.0</version>
</dependency>

Check out the Dominion API documentation as a reference to get started implementing your first app.

About Performance

Designing a high-performance and high-concurrency Java library is a piece of cake quite a challenge as the execution speed of Java code could be affected in many ways. Dominion mitigates Java's performance pitfalls by taking a few actions:

  • not just using the standard library: the Java standard library implements data structures and algorithms designed without making any assumption about the data as they are general purpose. Dominion implements some custom data structures and algorithms to increase performances and fill the gap with ECS frameworks written in system languages.
  • reducing garbage collection activities: GC could affect overall performances as its activities run concurrently with user code and without direct control. To reduce GC activities significantly, Dominion creates off-heap data structures whenever possible.
  • mastering concurrency: an ECS library must be not only fast but able to scale running on a multicore CPU, otherwise, it would make little sense today.
  • adding a blazing-fast logging layer: by implementing a thin logging layer over the standard System.Logger (Platform Logging API and Service - JEP 264), Dominion achieves a half nanosecond logging level check with next to no performance impact and does not require a specific dependency on the logging implementation of your choice.

Ok let's start

Here is a first example:

public class HelloDominion {

    public static void main(String[] args) {
        // creates your world
        Dominion hello = Dominion.create();

        // creates an entity with components
        hello.createEntity(
                "my-entity",
                new Position(0, 0),
                new Velocity(1, 1)
        );

        // creates a system
        Runnable system = () -> {
            //finds entities
            hello.findEntitiesWith(Position.class, Velocity.class)
                    // stream the results
                    .stream().forEach(result -> {
                        Position position = result.comp1();
                        Velocity velocity = result.comp2();
                        position.x += velocity.x;
                        position.y += velocity.y;
                        System.out.printf("Entity %s moved with %s to %s\n",
                                result.entity().getName(), velocity, position);
                    });
        };

        // creates a scheduler
        Scheduler scheduler = hello.createScheduler();
        // schedules the system
        scheduler.schedule(system);
        // starts 3 ticks per second
        scheduler.tickAtFixedRate(3);
    }

    // component types can be both classes and records

    static class Position {
        double x, y;

        public Position(double x, double y) {/*...*/}

        @Override
        public String toString() {/*...*/}
    }

    record Velocity(double x, double y) {
    }
}

Dominion Examples Page

Dominion comes with some documented sample apps to help adopt the solution, like the HelloDominion sample above.

The DarkEntities sample app has also been added, it could inspire a turn-based rogue-like game running on a terminal window:

dark-entities

Here the Dominion Examples page with all the documented code.

Join the Discord for further updates!

Credits

Java Profiler

IntelliJ Ultimate

Support Dominion

If you want to support Dominion project, consider giving it a Star ⭐️

Site

dominion-dev.github.io

dominion-ecs-java's People

Contributors

endison1986 avatar enricostara avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dominion-ecs-java's Issues

NullPointerException occurs

I'm tring to test a situation when i add entities and remove all after that, it occurs exception,
I fount when outer loop time less than 3 will not occurs, is this some kind of thread problem?

        // creates your world
        Dominion hello = Dominion.create();

        // creates a scheduler
        Scheduler scheduler = hello.createScheduler();

        // a system removes all entities with component Position
        Runnable s = () -> {
            hello.findEntitiesWith(Position.class).stream().forEach(e -> {
                hello.deleteEntity(e.entity());
            });
        };
        scheduler.schedule(s);

        for (int i = 0; i < 10; i++) {
            // add entities
            for (int j = 0; j < 1000; j++) {
                var e = hello.createEntity(
                        "my-entity",
                        new Position(0, 0),
                        new Velocity(1, 1)
                );
            }
            // tick to remove all entities
            scheduler.tick();
        }

Exception stacktrace:

dominion.SystemScheduler invoke 
java.lang.NullPointerException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:564)
	at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:591)
	at java.base/java.util.concurrent.ForkJoinTask.joinForPoolInvoke(ForkJoinTask.java:1042)
	at java.base/java.util.concurrent.ForkJoinPool.invoke(ForkJoinPool.java:2639)
	at dev.dominion.ecs.engine.SystemScheduler.forkAndJoin(SystemScheduler.java:113)
	at dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:262)
	at dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:239)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.NullPointerException: Cannot invoke "dev.dominion.ecs.engine.IntEntity$Data.composition()" because the return value of "dev.dominion.ecs.engine.IntEntity.getData()" is null
	at dev.dominion.ecs.engine.Composition$IteratorWith1.next(Composition.java:352)
	at dev.dominion.ecs.engine.Composition$IteratorWith1.next(Composition.java:340)
	at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1845)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
	at com.example.demo1.Demo1Application.lambda$main$1(Demo1Application.java:24)
	at dev.dominion.ecs.engine.SystemScheduler$2.compute(SystemScheduler.java:116)
	at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
 

`ClassIndex` does not handle duplicate identity hash codes

I may be wrong, but ClassIndex does not seem to handle System.identityHashCode returning the same for two distinct classes, as collision detection is only implemented based on the even smaller hashBits field. Which is an unlikely scenario, but not impossible.

Cannot found entity after some and/remove operation

Hi @enricostara , this is my test code

var d = Dominion.create();
var entity = d.createEntity(new A());
var s = d.createScheduler();
s.schedule(() -> {
    d.findEntitiesWith(A.class, C.class).stream().forEach(rs -> {
        try {
            System.out.println("AAAA");
            if (!rs.entity().has(B.class)) {
                rs.entity().add(new B());
            }
        } finally {
            rs.entity().removeType(C.class);
        }
    });
});
s.schedule(() -> {
    d.findEntitiesWith(A.class, B.class).stream().forEach(rs -> {
        System.out.println("BBBBBB");
    });
});
/// always handle the A.class
s.schedule(() -> {
    System.out.println("Before CCCC");
    d.findEntitiesWith(A.class).stream().forEach(rs -> {
        System.out.println("CCCCCCCCCCCC");
    });
});
s.tickAtFixedRate(1);
TimeUnit.SECONDS.sleep(2);
entity.add(new C());

I have three component (A,B,CοΌ‰and three system, always have a system to handle A.class. In the initial situation, the system operation is normal,but when I add C.class and add B.class then remove C.class, the system is not working

My version is 0.9.0-SNAPSHOT, you can run my test code and see the output.

When I create entities and add components in multiple threads, there are unforeseen concurrency issues

In my actual project, I create monster entities in a System. In order to take advantage of multi-core, I use parallelStream and create monster entities in it, and cache the Entity to Component, but I found that in the next System, the Entity in the Component obtained by the context.findEntitiesWith() method is different from the hashCode of ResultSet.entity()

example code like this.

public enum State {
    S1, S2, S3;
}

public static class C {
    private final Entity entity;

    public C(Entity entity) {
        this.entity = entity;
    }
}

public static class B {

}

public static class A {
    private C c;

    public A(Dominion dominion) {
        c = new C(dominion.createEntity(this));
        c.entity.setState(S1);
    }
}

public static void main(String[] args) {
    final Dominion dominion = Dominion.create();
    final var list = List.<Runnable>of(() -> {
        for (int i = 0; i < 200; i++) {
            final var a = new A(dominion);
            a.c.entity.add(new B());
        }
    }, () -> {
        for (int i = 0; i < 200; i++) {
            final var a = new A(dominion);
            a.c.entity.add(new B());
        }
    }, () -> {
        for (int i = 0; i < 200; i++) {
            final var a = new A(dominion);
            a.c.entity.add(new B());
        }
    });
    final var scheduler = dominion.createScheduler();
    scheduler.schedule(() -> list.parallelStream().forEach(Runnable::run));
    scheduler.schedule(() -> {
        dominion.findEntitiesWith(A.class).stream().forEach(rs->{
            if(rs.entity().hashCode() != rs.comp().c.entity.hashCode()) {
                System.out.println("1111111111111111111");
                System.exit(-1);
            }
        });
        System.exit(0);
    });
    scheduler.tickAtFixedRate(10);
}

If I remove a.c.entity.add(new B());, the program is fine.
If I change paralleStream() to stream(), this program is fine.

withState() doesn't seem to be working

This is an enum I'm using to categorize entities.

package components;

public enum EntityType {
    CUE, BALL, POCKET, WALL;
}

When I do

List<With1<EntityType>> cueResult = GameWorld.get().getDominion()
                .findEntitiesWith(EntityType.class)
                .stream().collect(Collectors.toList());

cueResult has size() 18 and contains With1[comp=CUE, entity=Entity={id=|0:5:0|-> [CUE, java.awt.Color[r=255,g=255,b=255], models.Transform2@3d75a2cf, colliders.ColliderComponent@788dcd3c, bodies.Body@55dc666e, components.TagCue@6ef8985a], stateId=|1:0:0|, enabled=true}]

When I do

List<With1<EntityType>> cueResult = GameWorld.get().getDominion()
                .findEntitiesWith(EntityType.class)
                .withState(EntityType.CUE)
                .stream().collect(Collectors.toList());

cueResult has size 0

Either I don't understand what withState() is intended to do or there is a bug.

In the meantime I've just used a tag class to accomplish the same goal

With1<TagCue> cueResult = GameWorld.get().getDominion().findEntitiesWith(TagCue.class).stream().findFirst().orElse(null);

QUESTION: referencing another entities and direct component access

I'm trying to implement a basic Transform component like in Godot or Unity.

The main idea is that the parent should affect the children, for that I need some way to reference them and then query the component.

I saw that there is an IntEntity with getId(), but this is not a public API and requests cannot be made using these identifiers. Also Entity doesn't have methods to access components.

Can we expect such functionality in the future or is there some other way to implement it?

Are there any logical loopholes in ClassIndex?

@enricostara hello friend, I have a question

This api ClassIndex.getIndexOrAddClasses(Class<?> klass) used for get or create index for component type. If hash codes for component types collide,Does it always return the index of the first component?

about thread safe

I use dominion on the server side,so there are some question about thread safe.
I need createEntity, addComponent or removeComponent, above action must run in System?
If I want to do above action in network thread,will there be any thread safety issues?

I have another question about IndexKey

@enricostara hi friend, I have another question about IndexKey
I read the source code of IndexKey,why the constructor IndexKey(int value) donot initialize the data field.
Will it cause it to obtain or overwrite the value of other IndexKey when it is used as the Key of the Map?

public final class IndexKey {
    private final int hashCode;
    private final byte[] data;

    public IndexKey(int value) {
        this.hashCode = value;
        data = null;
    }

    public IndexKey(int[] array) {...}

    public IndexKey(boolean[] checkArray, int min, int max, int length) {...}

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        return Arrays.equals(((IndexKey) o).data, data);
    }

    @Override
    public int hashCode() {
        return hashCode;
    }

    @Override
    public String toString() {...}
}

A question about Item

Hi @enricostara ,I have a question, are the Item interface and LinkedChunk generics necessary? Why not use the IntEntity array directly, so that there is no performance loss of unpacking?

how to get component from entity

how to get component from entity?

Dominion dominion= Dominion.create();
dominion.createEntity(comp1, comp2);
/// ....

final var scheduler = dominion.createSchduler();
scheduler.schedule(()->{
  dominion.findEntitiesWith(Component1.class, Component2.class).stream().forEach(rs->{
    final var entity = rs.entity();
    ///...
    if(entity.has(Component3.class)) {
        final var comp3 = entity.get(Component3.class);
    } else {
        fianl var comp3 = new Component3();
        ///... to config comp3
        entity.add(comp3);
    }
  });
});

scheduler.tickAtFixedRate(3);

NullPointerException and ArrayIndexOutOfBoundsException

@enricostara I define three component, A,B,C

public static class A{}
public static class B{}
public static class C{}

This is my test code:

it's fine.

public static void main(String[] args) {
    var w = Dominion.create();
    var s = w.createScheduler();
    s.schedule(()-> w.findEntitiesWith(A.class).stream().forEach((rs)-> System.out.println(rs.entity())));
    w.createEntity(new A());
    s.tickAtFixedRate(60);
}

I get NullPointerException

public static void main(String[] args) {
    var w = Dominion.create();
    var s = w.createScheduler();
    s.schedule(()-> w.findEntitiesWith(A.class, B.class).stream().forEach((rs)-> System.out.println(rs.entity())));
    w.createEntity(new A());
    s.tickAtFixedRate(60);
}
dominion.SystemScheduler invoke 
java.lang.NullPointerException
	at java.base/jdk.internal.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:564)
	at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:591)
	at java.base/java.util.concurrent.ForkJoinTask.joinForPoolInvoke(ForkJoinTask.java:1042)
	at java.base/java.util.concurrent.ForkJoinPool.invoke(ForkJoinPool.java:2639)
	at dev.dominion.ecs.engine.SystemScheduler.forkAndJoin(SystemScheduler.java:113)
	at dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:262)
	at dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:239)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.NullPointerException: Cannot load from int array because "this.componentIndex" is null
	at dev.dominion.ecs.engine.DataComposition.fetchComponentIndex(DataComposition.java:69)
	at dev.dominion.ecs.engine.DataComposition.select(DataComposition.java:173)
	at dev.dominion.ecs.engine.ResultSet$With2.compositionIterator(ResultSet.java:208)
	at dev.dominion.ecs.engine.ResultSet.iterator(ResultSet.java:56)
	at dev.dominion.ecs.engine.ResultSet.stream(ResultSet.java:85)
	at com.yunzhi.ancientsecrets.server.common.VirtualWorld.lambda$main$1(VirtualWorld.java:37)
	at dev.dominion.ecs.engine.SystemScheduler$2.compute(SystemScheduler.java:116)
	at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

it's fine

public static void main(String[] args) {
    var w = Dominion.create();
    var s = w.createScheduler();
    s.schedule(()-> w.findEntitiesWith(A.class, B.class).stream().forEach((rs)-> System.out.println(rs.entity())));
    w.createEntity(new A(), new B());
    s.tickAtFixedRate(60);
}

I get ArrayIndexOutOfBoundsException

public static void main(String[] args) {
    var w = Dominion.create();
    var s = w.createScheduler();
    s.schedule(()-> w.findEntitiesWith(A.class, B.class, C.class).stream().forEach((rs)-> System.out.println(rs.entity())));
    w.createEntity(new A(), new B());
    s.tickAtFixedRate(60);
}
dominion.SystemScheduler invoke 
java.lang.ArrayIndexOutOfBoundsException
	at java.base/jdk.internal.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:564)
	at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:591)
	at java.base/java.util.concurrent.ForkJoinTask.joinForPoolInvoke(ForkJoinTask.java:1042)
	at java.base/java.util.concurrent.ForkJoinPool.invoke(ForkJoinPool.java:2639)
	at dev.dominion.ecs.engine.SystemScheduler.forkAndJoin(SystemScheduler.java:113)
	at dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:262)
	at dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:239)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 2
	at dev.dominion.ecs.engine.collections.ChunkedPool$PoolMultiDataIterator.data(ChunkedPool.java:587)
	at dev.dominion.ecs.engine.DataComposition$IteratorWith3.next(DataComposition.java:356)
	at dev.dominion.ecs.engine.DataComposition$IteratorWith3.next(DataComposition.java:342)
	at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1845)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
	at com.yunzhi.ancientsecrets.server.common.VirtualWorld.lambda$main$1(VirtualWorld.java:37)
	at dev.dominion.ecs.engine.SystemScheduler$2.compute(SystemScheduler.java:116)
	at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

it's fine.

public static void main(String[] args) {
    var w = Dominion.create();
    var s = w.createScheduler();
    s.schedule(()-> w.findEntitiesWith(A.class, B.class).stream().forEach((rs)-> System.out.println(rs.entity())));
    w.createEntity(new A(), new B(), new C());
    s.tickAtFixedRate(60);
}

I get ArrayIndexOutOfBoundsException

public static void main(String[] args) {
    var w = Dominion.create();
    var s = w.createScheduler();
    s.schedule(()-> w.findEntitiesWith(A.class, B.class).stream().forEach((rs)-> System.out.println(rs.entity())));
    w.createEntity(new A(), new C());
    s.tickAtFixedRate(60);
}
dominion.SystemScheduler invoke 
java.lang.ArrayIndexOutOfBoundsException
	at java.base/jdk.internal.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:564)
	at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:591)
	at java.base/java.util.concurrent.ForkJoinTask.joinForPoolInvoke(ForkJoinTask.java:1042)
	at java.base/java.util.concurrent.ForkJoinPool.invoke(ForkJoinPool.java:2639)
	at dev.dominion.ecs.engine.SystemScheduler.forkAndJoin(SystemScheduler.java:113)
	at dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:262)
	at dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:239)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 2
	at dev.dominion.ecs.engine.collections.ChunkedPool$PoolMultiDataIterator.data(ChunkedPool.java:587)
	at dev.dominion.ecs.engine.DataComposition$IteratorWith2.next(DataComposition.java:321)
	at dev.dominion.ecs.engine.DataComposition$IteratorWith2.next(DataComposition.java:307)
	at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1845)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
	at com.yunzhi.ancientsecrets.server.common.VirtualWorld.lambda$main$1(VirtualWorld.java:37)
	at dev.dominion.ecs.engine.SystemScheduler$2.compute(SystemScheduler.java:116)
	at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

QUESTION: Is it possible to work with entities with more than 6 components?

Right now, the Dominion.findEntitiesWith() method accepts up to 6 component types. The returned Results object maps the types for each component by position and allows for their retrieval. Is there a mechanism in place to enable finding and working with entities with more than 6 components? Is this something that will come at a later stage?

When I created entities in parallel, some errors occurred

this code is fine.

public static void main(String[] args) {
    final Dominion dominion = Dominion.create();
    final var list = List.<Runnable>of(() -> {
        for (int i = 0; i < 100; i++) {
            dominion.createEntity(new A()).add(new B());
        }
    }, () -> {
        for (int i = 0; i < 100; i++) {
            dominion.createEntity(new A()).add(new B());
        }
    }, () -> {
        for (int i = 0; i < 100; i++) {
            dominion.createEntity(new A()).add(new B());
        }
    });
    final var scheduler = dominion.createScheduler();
    scheduler.schedule(() -> list.parallelStream().forEach(Runnable::run));
    scheduler.tickAtFixedRate(10);
}

this code is also fine.

public static void main(String[] args) {
    final Dominion dominion = Dominion.create();
    final var list = List.<Runnable>of(() -> {
        for (int i = 0; i < 100; i++) {
            dominion.createEntity(new A()).add(new B());
        }
    }, () -> {
        for (int i = 0; i < 100; i++) {
            dominion.createEntity(new A()).add(new B());
        }
    }, () -> {
        for (int i = 0; i < 100; i++) {
            dominion.createEntity(new A()).add(new B()).add(new C());
        }
    });
    final var scheduler = dominion.createScheduler();
    scheduler.schedule(() -> list.stream().forEach(Runnable::run));
    scheduler.tickAtFixedRate(10);
}

but I get some error with this code

public static void main(String[] args) {
    final Dominion dominion = Dominion.create();
    final var list = List.<Runnable>of(() -> {
        for (int i = 0; i < 100; i++) {
            dominion.createEntity(new A()).add(new B());
        }
    }, () -> {
        for (int i = 0; i < 100; i++) {
            dominion.createEntity(new A()).add(new B());
        }
    }, () -> {
        for (int i = 0; i < 100; i++) {
            dominion.createEntity(new A()).add(new B()).add(new C());
        }
    });
    final var scheduler = dominion.createScheduler();
    scheduler.schedule(() -> list.parallelStream().forEach(Runnable::run));

    scheduler.tickAtFixedRate(10);
}
  δΈ₯重         dominion.SystemScheduler invoke 
java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: Invalid chunkById [LinkedChunk={id=1, dataLength=1, capacity=4096, size=0, previous=null, next=null, of Tenant={id=1, dataLength=1, nextId=|0:1:0|, subject=root}}] retrieved by [4097]
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:562)
	at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:591)
	at java.base/java.util.concurrent.ForkJoinTask.joinForPoolInvoke(ForkJoinTask.java:1042)
	at java.base/java.util.concurrent.ForkJoinPool.invoke(ForkJoinPool.java:2639)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler.forkAndJoin(SystemScheduler.java:113)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:262)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:239)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: Invalid chunkById [LinkedChunk={id=1, dataLength=1, capacity=4096, size=0, previous=null, next=null, of Tenant={id=1, dataLength=1, nextId=|0:1:0|, subject=root}}] retrieved by [4097]
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:562)
	at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:591)
	at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:689)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:159)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:765)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.Test.lambda$main$3(Test.java:38)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler$2.compute(SystemScheduler.java:116)
	at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
Caused by: java.lang.IllegalArgumentException: Invalid chunkById [LinkedChunk={id=1, dataLength=1, capacity=4096, size=0, previous=null, next=null, of Tenant={id=1, dataLength=1, nextId=|0:1:0|, subject=root}}] retrieved by [4097]
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.collections.ChunkedPool$Tenant.freeId(ChunkedPool.java:273)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.collections.ChunkedPool$Tenant.freeId(ChunkedPool.java:263)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.CompositionRepository.modifyComponents(CompositionRepository.java:167)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.CompositionRepository.addComponent(CompositionRepository.java:185)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.IntEntity.add(IntEntity.java:91)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.Test.lambda$main$2(Test.java:34)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
	at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290)
	at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:754)
	... 5 more

and this code

public static void main(String[] args) {
    final Dominion dominion = Dominion.create();
    final var list = List.<Runnable>of(() -> {
        for (int i = 0; i < 100; i++) {
            final var entity = dominion.createEntity(new A()).setState(S1);
            entity.add(new B());
            entity.setState(S2);
        }
    }, () -> {
        for (int i = 0; i < 100; i++) {
            final var entity = dominion.createEntity(new A()).setState(S1);
            entity.add(new B());
            entity.setState(S2);
        }
    }, () -> {
        for (int i = 0; i < 100; i++) {
            final var entity = dominion.createEntity(new A()).setState(S1);
            entity.add(new B());
            entity.setState(S3);
        }
    });
    final var scheduler = dominion.createScheduler();
    scheduler.schedule(() -> list.parallelStream().forEach(Runnable::run));
    scheduler.tickAtFixedRate(10);
}
  δΈ₯重         dominion.SystemScheduler invoke 
java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: Invalid chunkById [LinkedChunk={id=2, dataLength=0, capacity=4096, size=1, previous=null, next=null, of Tenant={id=2, dataLength=0, nextId=|0:2:0|, subject=|1085:[4, 0]|}}] retrieved by [8194]
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:562)
	at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:591)
	at java.base/java.util.concurrent.ForkJoinTask.joinForPoolInvoke(ForkJoinTask.java:1042)
	at java.base/java.util.concurrent.ForkJoinPool.invoke(ForkJoinPool.java:2639)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler.forkAndJoin(SystemScheduler.java:113)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:262)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:239)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: Invalid chunkById [LinkedChunk={id=2, dataLength=0, capacity=4096, size=1, previous=null, next=null, of Tenant={id=2, dataLength=0, nextId=|0:2:0|, subject=|1085:[4, 0]|}}] retrieved by [8194]
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:562)
	at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:591)
	at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:689)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:159)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:765)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.FixBug.lambda$main$3(FixBug.java:51)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler$2.compute(SystemScheduler.java:116)
	at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
Caused by: java.lang.IllegalArgumentException: Invalid chunkById [LinkedChunk={id=2, dataLength=0, capacity=4096, size=1, previous=null, next=null, of Tenant={id=2, dataLength=0, nextId=|0:2:0|, subject=|1085:[4, 0]|}}] retrieved by [8194]
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.collections.ChunkedPool$Tenant.freeId(ChunkedPool.java:273)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.collections.ChunkedPool$Tenant.freeStateId(ChunkedPool.java:267)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.IntEntity.setState(IntEntity.java:168)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.FixBug.lambda$main$0(FixBug.java:35)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
	at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290)
	at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:754)
	... 5 more

Hi @enricostara I get a new error when I add a second component `B`

Hi @enricostara I get a new error when I add a second component B

public static void main(String[] args) {
    var d = Dominion.create();
    var s = d.createScheduler();
    s.schedule(() -> {
        d.findEntitiesWith(A.class, B.class).iterator();
    });

    for(var i = 0; i < 50; i++) {
        new Thread(()-> {
            for(var j = 0; j< 100; j++) {
                d.createEntity(new A(), new B());
            }
        }).start();
    }

    s.tickAtFixedRate(3);
}
Exception in thread "Thread-0" Exception in thread "Thread-9" Exception in thread "Thread-13" Exception in thread "Thread-2" Exception in thread "Thread-4" Exception in thread "Thread-10" Exception in thread "Thread-15" Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
Exception in thread "Thread-12" Exception in thread "Thread-14" Exception in thread "Thread-7" java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
Exception in thread "Thread-8" java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
Exception in thread "Thread-5" Exception in thread "Thread-11" Exception in thread "Thread-6" java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 4
	at dev.dominion.ecs.engine.system.ClassIndex.getIndexKey(ClassIndex.java:153)
	at dev.dominion.ecs.engine.CompositionRepository.getOrCreate(CompositionRepository.java:92)
	at dev.dominion.ecs.engine.EntityRepository.createEntity(EntityRepository.java:44)
	at dev.dominion.ecs.engine.TestClass.lambda$main$1(VirtualWorld.java:53)
	at java.base/java.lang.Thread.run(Thread.java:833)

Originally posted by @endison1986 in #117 (comment)

A concurrency issue

During development, I got an exception log that looked like it was looping to a null value. So I wrote a piece of test code to try to restore the error. Judging from the test code, this should be a concurrency issue.

Is this result expected?

dominion.SystemScheduler invoke 
java.lang.NullPointerException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:564)
	at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:591)
	at java.base/java.util.concurrent.ForkJoinTask.joinForPoolInvoke(ForkJoinTask.java:1042)
	at java.base/java.util.concurrent.ForkJoinPool.invoke(ForkJoinPool.java:2639)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler.forkAndJoin(SystemScheduler.java:113)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:280)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler$Single.call(SystemScheduler.java:257)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.NullPointerException: Cannot invoke "dev.dominion.ecs.engine.collections.ChunkedPool$Item.getChunk()" because "item" is null
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.collections.ChunkedPool$PoolDataIteratorWithState.next(ChunkedPool.java:754)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.DataComposition$IteratorWith1Next.next(DataComposition.java:306)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.DataComposition$IteratorWith1Next.next(DataComposition.java:294)
	at java.base/java.lang.Iterable.forEach(Iterable.java:74)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.Test.lambda$main$0(Test.java:34)
	at dev.dominion.ecs.engine/dev.dominion.ecs.engine.SystemScheduler$2.compute(SystemScheduler.java:116)
	at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

Test code:

public class Test {
    public enum S {
        A, B
    }
    public record A(int id) {
    }
    public static void consume(Object c) {
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) {
        EntityRepository entityRepository = (EntityRepository) new EntityRepository.Factory().create("test");
        final var list = new ArrayList<Entity>(1000);
        for (int i = 0; i < 1000; i++) {
            list.add(entityRepository.createEntity(new A(i)).setState(S.A));
        }
        Scheduler scheduler = entityRepository.createScheduler();

        scheduler.schedule(() -> entityRepository.findEntitiesWith(A.class).withState(S.A).forEach(Test::consume));


        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                    for (int i = 0; i < 1000; i++) {
                        list.get(i).setState(S.B);
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();

        scheduler.tickAtFixedRate(20);
    }
}

Add Ability to Retrieve All Entities

Description

Would it be possible to add an option to the API to retrieve all entities?

I am currently using a workaround where I add a dummy component to all entities and then just do:

dominion.findEntitiesWith(FillerComponent.class).iterator();

And then just iterate through it to get a list of all my entities, while it works, it would be nicer if I did not have to add a dummy component.

Use case

I wanted to fetch all entities for a debug menu so that I could implement a GUI where I can just click to select an entity from the list to add or remove a component from UI during testing. This is a lot faster than having to make all component changes programmatically.

Bug with Inheritance

Through my time of attempting to use this ecs I noticed one glaring problem. That problem being that this ecs does not play nicely with components that utilize inheritance. In the example below we create two entities where one uses the super class component and the other uses the subclass component.

public class Main {
    public static void main(String[] args) {
        Dominion dom = Dominion.create();
        dom.createEntity(new DemoA());
        dom.createEntity(new DemoB());
        Scheduler scheduler = dom.createScheduler();
        scheduler.schedule(() -> {
            System.out.println("ALL DEMOA COMPONENTS");
            dom.findCompositionsWith(DemoA.class)
            .stream().forEach(r -> r.onExec());
        });
        scheduler.schedule(() -> {
            System.out.println("ALL DEMOB COMPONENTS");
            dom.findCompositionsWith(DemoB.class)
            .stream().forEach(r -> r.onExec());
        });    
        scheduler.tick();
        scheduler.shutDown();
    }
    private static class DemoA {
        public void onExec() {
            System.out.println("A");
        }
    }
    private static class DemoB extends DemoA {
        @Override
        public void onExec() {
            System.out.println("B");
        }
    }
}

Now the expected result is that since DemoB inheriets from DemoA, when we query for compositions utalizing DemoA, the entitiy that has DemoB would be type casted into DemoA producing the result below:

ALL DEMOA COMPONENTS
A
B
ALL DEMOB COMPONENTS
B

However when the example is run it produces the following result:

ALL DEMOA COMPONENTS
A
ALL DEMOB COMPONENTS
B

Regardless of if this is a bug or the intended implementation by the author I believe that dominion needs to support inheritance as it is a must have in more complex systems that benefits from the ECS paradigm such as physics and graphics engines.

Test Failures

When I run 'mvn package', some tests are failing;

[ERROR] SystemSchedulerTest.deltaTime:164 expected: <0.5> but was: <0.5152623>
[ERROR] SystemSchedulerTest.tickAtFixedRate:148 expected: <6> but was: <7>

I'm using Microsoft OpenJDK21.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.