dominion-dev / dominion-ecs-java

Insanely fast ECS (Entity Component System) for Java
https://dominion-dev.github.io
MIT License
288 stars 18 forks source link

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

Closed endison1986 closed 1 year ago

endison1986 commented 1 year ago

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 https://github.com/dominion-dev/dominion-ecs-java/issues/117#issuecomment-1362534115

endison1986 commented 1 year ago

About this issue, I analysis source code, and found that the problem might be here. dev.dominion.ecs.engine.system.ClassIndex.java

public IndexKey getIndexKey(Object[] objects) {
    int length = objects.length;
    boolean[] checkArray = new boolean[index + length + 1];
    int min = Integer.MAX_VALUE, max = 0;
    for (int i = 0; i < length; i++) {
        int value = getIndex(objects[i].getClass());
        value = value == 0 ? getIndexOrAddClass(objects[i].getClass()) : value;
        if (checkArray[value]) {
            throw new IllegalArgumentException("Duplicate object types are not allowed");
        }
        checkArray[value] = true;
        min = Math.min(value, min);
        max = Math.max(value, max);
    }
    return new IndexKey(checkArray, min, max, length);
}

under lines has concurrency problem.

int value = getIndex(objects[i].getClass());
value = value == 0 ? getIndexOrAddClass(objects[i].getClass()) : value;

If I createEntity in multiple threads, getIndex method will be called by multiple thread with result in zero. Also the getIndexOrAddClass method will be called multiple times. So I add a synchronized block to fix this issue temporary, Hope you have better way.

private final Object lock = new Object();
@SuppressWarnings("ForLoopReplaceableByForEach")
public IndexKey getIndexKey(Object[] objects) {
    int length = objects.length;
    boolean[] checkArray = new boolean[index + length + 1];
    int min = Integer.MAX_VALUE, max = 0;
    for (int i = 0; i < length; i++) {
        var componentClass = objects[i].getClass();
        var value = getIndex(componentClass);
        if (value == 0) {
            synchronized (lock) {
                value = getIndex(componentClass);
                if (value == 0) {
                    value = getIndexOrAddClass(componentClass);
                }
            }
        }
        if (checkArray[value]) {
            throw new IllegalArgumentException("Duplicate object types are not allowed");
        }
        checkArray[value] = true;
        min = Math.min(value, min);
        max = Math.max(value, max);
    }
    return new IndexKey(checkArray, min, max, length);
}
enricostara commented 1 year ago

Thanks for reporting, I'll look into this issue after I fix issue #117 (still in progress)

enricostara commented 1 year ago

Hi @endison1986, issue fixed by #122