dominion-dev/dominion-ecs-java

about thread safe

endison1986 opened this issue · 14 comments

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?

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

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.

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.

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)

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

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

@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.

@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

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)

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

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

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).

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

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