Skip to content

Commit 35d65bf

Browse files
committed
Attempt to implement multithreaded entity creation with stage
1 parent f430bf6 commit 35d65bf

7 files changed

Lines changed: 134 additions & 38 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.github.elebras1.flecs.examples;
2+
3+
import com.github.elebras1.flecs.Entity;
4+
import com.github.elebras1.flecs.Flecs;
5+
import com.github.elebras1.flecs.Query;
6+
import com.github.elebras1.flecs.examples.components.Minister;
7+
import com.github.elebras1.flecs.examples.components.Position;
8+
9+
import java.util.concurrent.ExecutorService;
10+
import java.util.concurrent.Executors;
11+
import java.util.concurrent.TimeUnit;
12+
import java.util.concurrent.atomic.AtomicInteger;
13+
14+
public class StageExample {
15+
16+
public static void main(String[] args) throws InterruptedException {
17+
int totalEntities = 100_000;
18+
int threadCount = 4;
19+
int entitiesPerThread = totalEntities / threadCount;
20+
AtomicInteger entityCounter = new AtomicInteger(0);
21+
22+
try (Flecs world = new Flecs()) {
23+
24+
world.component(Position.class);
25+
world.setStageCount(threadCount);
26+
27+
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
28+
29+
for (int t = 0; t < threadCount; t++) {
30+
final int tid = t;
31+
32+
executor.submit(() -> {
33+
Flecs.runScoped(() -> {
34+
try (Flecs stage = world.getStage(tid)) {
35+
System.out.println("Creating " + tid + " entities using " + threadCount + " threads...");
36+
37+
for (int i = 0; i < entitiesPerThread; i++) {
38+
long entityId = stage.entity();
39+
Entity entity = stage.obtainEntity(entityId);
40+
41+
entity.set(new Position(10.0f, 20.0f));
42+
43+
int count = entityCounter.incrementAndGet();
44+
System.out.printf("Created entity %d (count %d)%n", entityId, count);
45+
}
46+
}
47+
});
48+
});
49+
}
50+
51+
executor.shutdown();
52+
executor.awaitTermination(60, TimeUnit.SECONDS);
53+
54+
for (int t = 0; t < threadCount; t++) {
55+
try (Flecs stageWrapper = world.getStage(t)) {
56+
stageWrapper.merge();
57+
}
58+
}
59+
60+
Flecs.runScoped(() -> {
61+
try (Query q = world.query().with(Minister.class).build()) {
62+
System.out.println("Querying entities...");
63+
final int[] count = {0};
64+
q.each(_ -> count[0]++);
65+
System.out.println("Total entities found: " + count[0]);
66+
}
67+
});
68+
}
69+
}
70+
}

src/main/java/com/github/elebras1/flecs/Component.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package com.github.elebras1.flecs;
22

3+
import java.lang.foreign.Arena;
34
import java.lang.foreign.MemoryLayout;
45
import java.lang.foreign.MemorySegment;
56

67
public interface Component<T> {
78

89
MemoryLayout layout();
910

10-
void write(MemorySegment segment, T data, Flecs world);
11+
void write(MemorySegment segment, T data, Arena arena);
1112

1213
T read(MemorySegment segment);
1314

src/main/java/com/github/elebras1/flecs/ComponentHooks.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ private void writeComponentArray(MemorySegment ptr, T[] components, int count) {
263263
for (int i = 0; i < count; i++) {
264264
if (components[i] != null) {
265265
MemorySegment componentSegment = buffer.asSlice(i * size, size);
266-
this.component.write(componentSegment, components[i], this.world);
266+
this.component.write(componentSegment, components[i], this.world.arena());
267267
}
268268
}
269269
}

src/main/java/com/github/elebras1/flecs/Entity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public <T> Entity set(T data) {
115115
Component<T> component = this.world.componentRegistry().getComponent(componentClass);
116116

117117
MemorySegment dataSegment = this.world.getComponentBuffer(component.size());
118-
component.write(dataSegment, data, this.world);
118+
component.write(dataSegment, data, this.world.arena());
119119
flecs_h.ecs_set_id(this.world.nativeHandle(), this.id, componentId, component.size(), dataSegment);
120120

121121
return this;

src/main/java/com/github/elebras1/flecs/Flecs.java

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,15 @@
1616
import static java.lang.foreign.ValueLayout.JAVA_LONG;
1717

1818
public class Flecs implements AutoCloseable {
19-
2019
private final MemorySegment nativeWorld;
2120
private final Arena arena;
2221
private final ComponentRegistry componentRegistry;
23-
private boolean closed = false;
24-
private final ThreadLocal<NameBuffer> threadLocalNameBuffer;
25-
private final ThreadLocal<ComponentBuffer> threadLocalComponentBuffer;
26-
private final ThreadLocal<EntityDescBuffer> threadLocalEntityDesc;
2722
private final Map<Long, SystemCallbacks> systemCallbacks;
2823
private final Map<Long, ObserverCallbacks> observerCallbacks;
24+
private static final ScopedValue<FlecsBuffers> CONTEXT = ScopedValue.newInstance();
25+
private final FlecsBuffers defaultBuffers;
26+
private final boolean owned;
27+
private boolean closed;
2928

3029
static {
3130
FlecsLoader.load();
@@ -37,6 +36,19 @@ private record SystemCallbacks(Query.IterCallback iterCallback, Query.RunCallbac
3736
private record ObserverCallbacks(Query.IterCallback iterCallback, Query.RunCallback runCallback, Query.EntityCallback entityCallback) {
3837
}
3938

39+
public record FlecsBuffers(NameBuffer nameBuffer, ComponentBuffer componentBuffer, EntityDescBuffer entityDescBuffer) implements AutoCloseable {
40+
public FlecsBuffers() {
41+
this(new NameBuffer(64), new ComponentBuffer(256), new EntityDescBuffer());
42+
}
43+
44+
@Override
45+
public void close() {
46+
this.nameBuffer.close();
47+
this.componentBuffer.close();
48+
this.entityDescBuffer.close();
49+
}
50+
}
51+
4052
private static final class NameBuffer implements AutoCloseable {
4153
private Arena arena;
4254
private MemorySegment segment;
@@ -118,30 +130,30 @@ public void close() {
118130
}
119131

120132
public Flecs() {
121-
this.arena = Arena.ofConfined();
133+
this.arena = Arena.ofShared();
122134
this.nativeWorld = flecs_h.ecs_init();
123135

124136
if (this.nativeWorld == null || this.nativeWorld.address() == 0) {
125137
throw new IllegalStateException("Flecs world initialization failed");
126138
}
127139

128140
this.componentRegistry = new ComponentRegistry(this);
129-
this.threadLocalNameBuffer = ThreadLocal.withInitial(() -> new NameBuffer(64));
130-
this.threadLocalComponentBuffer = ThreadLocal.withInitial(() -> new ComponentBuffer(256));
131-
this.threadLocalEntityDesc = ThreadLocal.withInitial(EntityDescBuffer::new);
132141
this.systemCallbacks = new ConcurrentHashMap<>();
133142
this.observerCallbacks = new ConcurrentHashMap<>();
143+
this.defaultBuffers = new FlecsBuffers();
144+
this.closed = false;
145+
this.owned = true;
134146
}
135147

136148
private Flecs(MemorySegment stagePtr, ComponentRegistry sharedRegistry) {
137-
this.arena = null;
149+
this.arena = Arena.ofConfined();
138150
this.nativeWorld = stagePtr;
139151
this.componentRegistry = sharedRegistry;
140-
this.threadLocalNameBuffer = ThreadLocal.withInitial(() -> new NameBuffer(64));
141-
this.threadLocalComponentBuffer = ThreadLocal.withInitial(() -> new ComponentBuffer(256));
142-
this.threadLocalEntityDesc = ThreadLocal.withInitial(EntityDescBuffer::new);
143152
this.systemCallbacks = new ConcurrentHashMap<>();
144153
this.observerCallbacks = new ConcurrentHashMap<>();
154+
this.defaultBuffers = null;
155+
this.closed = false;
156+
this.owned = false;
145157
}
146158

147159
public long entity() {
@@ -156,14 +168,15 @@ public long entity(String name) {
156168
byte[] utf8 = name.getBytes(StandardCharsets.UTF_8);
157169
int len = utf8.length;
158170

159-
NameBuffer nameBuffer = this.threadLocalNameBuffer.get();
160-
MemorySegment nameSegment = nameBuffer.ensure(len + 1);
171+
FlecsBuffers buffers = this.getBuffers();
172+
MemorySegment nameSegment = buffers.nameBuffer().ensure(len + 1);
161173
nameSegment.asSlice(0, len).copyFrom(MemorySegment.ofArray(utf8));
162174
nameSegment.set(ValueLayout.JAVA_BYTE, len, (byte)0);
163175

164-
MemorySegment desc = this.threadLocalEntityDesc.get().get();
176+
MemorySegment desc = buffers.entityDescBuffer().get();
165177
desc.fill((byte) 0);
166178
ecs_entity_desc_t.name(desc, nameSegment);
179+
167180
return flecs_h.ecs_entity_init(this.nativeWorld, desc);
168181
}
169182

@@ -175,7 +188,8 @@ public Entity obtainEntity(long entityId) {
175188
}
176189

177190
MemorySegment getComponentBuffer(long size) {
178-
return this.threadLocalComponentBuffer.get().ensure(size);
191+
FlecsBuffers buffers = this.getBuffers();
192+
return buffers.componentBuffer().ensure(size);
179193
}
180194

181195
public EcsLongList entityBulk(int count) {
@@ -293,8 +307,8 @@ public long lookup(String name) {
293307
byte[] utf8 = name.getBytes(StandardCharsets.UTF_8);
294308
int len = utf8.length;
295309

296-
NameBuffer buffer = this.threadLocalNameBuffer.get();
297-
MemorySegment segment = buffer.ensure(len + 1);
310+
FlecsBuffers buffers = this.getBuffers();
311+
MemorySegment segment = buffers.nameBuffer().ensure(len + 1);
298312

299313
segment.asSlice(0, len).copyFrom(MemorySegment.ofArray(utf8));
300314
segment.set(ValueLayout.JAVA_BYTE, len, (byte)0);
@@ -571,6 +585,22 @@ void registerObserverCallbacks(long observerId, Query.IterCallback iterCallback,
571585
}
572586
}
573587

588+
private FlecsBuffers getBuffers() {
589+
if (CONTEXT.isBound()) {
590+
return CONTEXT.get();
591+
} else if (this.defaultBuffers != null) {
592+
return this.defaultBuffers;
593+
} else {
594+
throw new IllegalStateException("No FlecsBuffers available in this context");
595+
}
596+
}
597+
598+
public static void runScoped(Runnable runnable) {
599+
try (var buffers = new FlecsBuffers()) {
600+
ScopedValue.where(CONTEXT, buffers).run(runnable);
601+
}
602+
}
603+
574604
public void setStageCount(int stages) {
575605
this.checkClosed();
576606
flecs_h.ecs_set_stage_count(this.nativeWorld, stages);
@@ -722,20 +752,15 @@ public void fromJson(String json) {
722752
@Override
723753
public void close() {
724754
if (!this.closed) {
725-
this.closed = true;
726-
if (this.nativeWorld != null && this.nativeWorld.address() != 0 && this.arena != null) {
755+
if (this.owned && this.nativeWorld != null && this.nativeWorld.address() != 0) {
727756
flecs_h.ecs_fini(this.nativeWorld);
728757
}
758+
729759
if (this.arena != null) {
730760
this.arena.close();
731761
}
732762
}
733-
this.threadLocalNameBuffer.get().close();
734-
this.threadLocalComponentBuffer.get().close();
735-
this.threadLocalEntityDesc.get().close();
736-
this.threadLocalNameBuffer.remove();
737-
this.threadLocalComponentBuffer.remove();
738-
this.threadLocalEntityDesc.remove();
763+
this.closed = true;
739764
}
740765

741766
@Override

src/main/java/com/github/elebras1/flecs/processor/ComponentCodeGenerator.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.palantir.javapoet.*;
55

66
import javax.lang.model.element.*;
7+
import java.lang.foreign.Arena;
78
import java.lang.foreign.MemoryLayout;
89
import java.lang.foreign.MemorySegment;
910
import java.util.List;
@@ -57,7 +58,7 @@ private FieldSpec createLayoutField(String recordName, List<VariableElement> fie
5758
VariableElement field = fields.get(i);
5859
String fieldName = field.getSimpleName().toString();
5960
String layoutMethod = getLayoutMethod(field.asType().toString());
60-
61+
6162
layoutBuilder.add("$L.$L().withName($S)", LAYOUT_FIELD_CLASS, layoutMethod, fieldName);
6263
if (i < fields.size() - 1) {
6364
layoutBuilder.add(",\n");
@@ -104,14 +105,14 @@ private MethodSpec createWriteMethod(String recordName, List<VariableElement> fi
104105
.addModifiers(Modifier.PUBLIC)
105106
.addParameter(MemorySegment.class, "segment")
106107
.addParameter(TypeVariableName.get(recordName), "data")
107-
.addParameter(ClassName.get("com.github.elebras1.flecs", "Flecs"), "world"); // AJOUTER
108+
.addParameter(Arena.class, "arena");
108109

109110
for (VariableElement field : fields) {
110111
String fieldName = field.getSimpleName().toString();
111112
String offsetName = "OFFSET_" + fieldName.toUpperCase();
112113
String typeName = field.asType().toString();
113114
if ("java.lang.String".equals(typeName)) {
114-
method.addStatement("$L.set(segment, $L, data.$L(), world)", LAYOUT_FIELD_CLASS, offsetName, fieldName);
115+
method.addStatement("$L.set(segment, $L, data.$L(), arena)", LAYOUT_FIELD_CLASS, offsetName, fieldName);
115116
} else {
116117
method.addStatement("$L.set(segment, $L, data.$L())", LAYOUT_FIELD_CLASS, offsetName, fieldName);
117118
}
@@ -132,7 +133,7 @@ private MethodSpec createReadMethod(String packageName, String recordName, List<
132133
String offsetName = "OFFSET_" + fieldName.toUpperCase();
133134
String typeName = field.asType().toString();
134135
String getterMethod = getGetterMethod(typeName);
135-
136+
136137
method.addStatement("$L $L = $L.$L(segment, $L)", typeName, fieldName, LAYOUT_FIELD_CLASS, getterMethod, offsetName);
137138
}
138139

src/main/java/com/github/elebras1/flecs/util/LayoutField.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.github.elebras1.flecs.util;
22

3-
import com.github.elebras1.flecs.Flecs;
4-
3+
import java.lang.foreign.Arena;
54
import java.lang.foreign.MemoryLayout;
65
import java.lang.foreign.MemorySegment;
76
import java.lang.foreign.ValueLayout;
@@ -62,11 +61,11 @@ public static void set(MemorySegment segment, long offset, boolean value) {
6261
segment.set(ValueLayout.JAVA_BOOLEAN, offset, value);
6362
}
6463

65-
public static void set(MemorySegment segment, long offset, String value, Flecs world) {
64+
public static void set(MemorySegment segment, long offset, String value, Arena arena) {
6665
if (value == null) {
6766
segment.set(ValueLayout.ADDRESS, offset, MemorySegment.NULL);
6867
} else {
69-
MemorySegment stringSegment = world.arena().allocateFrom(value);
68+
MemorySegment stringSegment = arena.allocateFrom(value);
7069
segment.set(ValueLayout.ADDRESS, offset, stringSegment);
7170
}
7271
}

0 commit comments

Comments
 (0)