dominion-dev / dominion-ecs-java

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

When I created entities in parallel, some errors occurred #164

Closed endison1986 closed 1 year ago

endison1986 commented 1 year ago

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
endison1986 commented 1 year ago

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.

enricostara commented 1 year ago

Hi @endison1986 , the last message seems to be a different problem, could you please open a different issue and move the message into the new issue? thanks

endison1986 commented 1 year ago

Ok, I've moved the problem to a new issue @enricostara I am very happy to hear from you. I analyzed it all morning and couldn't find the cause of the problem. But there is some progress, I will add information in the new issue.

enricostara commented 1 year ago

Hi @endison1986, the fix has been merged as EA and is available by using the last 0.9.0-SNAPSHOT, let me know if it works as expected

endison1986 commented 1 year ago

Thanks, @enricostara This problem has been fixed in the test code, and I tested in my actual project, no errors reported. But this problem was discovered when I reproduced #156, not in my actual project.

enricostara commented 1 year ago

Thanks @endison1986 , issue #165 is completely different and not even related to this one. Then I'm closing this as it is already covered by new tests