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

about thread safe #117

Closed endison1986 closed 1 year ago

endison1986 commented 1 year ago

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?

endison1986 commented 1 year ago

Are there any examples of usage on the server side, such as how to handle packet from network.

enricostara commented 1 year ago

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?

You don't need to follow any special rules to create your systems in Dominion, it can be any Runnable, Callable or even simple methods you want. Then you can safely run in network threads.

enricostara commented 1 year ago

Are there any examples of usage on the server side, such as how to handle packet from network.

I have no special examples for this. As a general rule, you might have network threads about creating an entity for each new request with a "Request" component tag + other components that can better define packets, and several parallel/specialized systems to process all these different entity types and to provide an appropriate response. Might not be applicable in all scenarios, but just an idea of how to apply ECS models in such a context.

endison1986 commented 1 year ago

thanks for your reply,if my understanding is correct,I can create entity in network thread and do my game logic in systems.

public class A {}
public static void main(String[] args) {
    var d = Dominion.create();
    var s = d.createScheduler();
    s.schedule(() -> {
        d.findEntitiesWith(A.class).stream().forEach(rs -> {
            rs.entity().removeType(A.class);
        });
    });

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

    s.tickAtFixedRate(3);
}

this is my test code, I run and get some error message, please help me,thanks

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.api.Entity.removeType(java.lang.Class)" because the return value of "dev.dominion.ecs.api.Results$With1.entity()" is null
    at com.test.Test.lambda$main$0(Test.java:11)
    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.test.Test.lambda$main$1(Test.java:10)
    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)
endison1986 commented 1 year ago

can you help me, please @enricostara 🙂 I don't know whether my method is wrong

enricostara commented 1 year ago

Hi @endison1986 , your method it's fine, let me perform some checks

endison1986 commented 1 year ago

@enricostara I'm so happy to received your reply and will wait for you to fix this issue. Dominion-ECS is powerful, fast and easy to use ECS framework. I like it and hope it be better and better sincerely, thank you and good luck friend.

enricostara commented 1 year ago

@endison1986 Thanks so much for your support! We lacked concurrency unit tests in entity creation. I'm working on adding the missing tests and fixing the code. It will take a few days

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

Hi @enricostara I analysis source code and found that it is not the reason for multithreading I run under code and get the error also.

public static void main(String[] args) throws InterruptedException {
    var d = Dominion.create();
    var s = d.createScheduler();
    for (var j = 0; j < 4096; j++) {
        d.createEntity(new A(), new B());
    }
    s.schedule(() -> d.findEntitiesWith(A.class, B.class).stream().forEach(rs -> rs.entity().removeType(A.class)));
    s.tickAtFixedRate(3);
}

Get you also found the problem in 4096, if modify to 4095, the program will run normally! So I suspect it's LinkedChunk's problem

endison1986 commented 1 year ago

I looked at the source code for a day today, and I think the problem probably lies here image currentChunk set as new chunk after prev chunk is full, and notCurrentChunk will always true, than the index field in LinkedChunk while always be chunkCaptcity - 1. image

enricostara commented 1 year ago

Hi @endison1986, thanks for the analysis. As already mentioned, this issue is going to require some fundamental changes. It's not only related to thread safety, but I've discovered at least two other issues related to this. Unfortunately, as I'm traveling again for the holidays, I won't be able to work on it 100% these days, but I should be able to fix it by next week (I hope).

endison1986 commented 1 year ago

Ok @enricostara , I read the source code again, not only increased my understanding of the source code, but also learned more programming ideas and skills, thank you for your efforts, and look forward to your results

enricostara commented 1 year ago

Hi @endison1986, please run with the latest 0.8.0-SNAPSHOT to check if this issue is fixed