Skip to content

Commit d225205

Browse files
Implement multi-threaded queries
1 parent 092b151 commit d225205

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+25973
-31
lines changed

src/Flecs.NET.Codegen/Generators/IIterable.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,27 @@ public static string GenerateExtensions(Type type, int i)
102102
}
103103
""");
104104

105+
IEnumerable<string> jobIterators = Generator.CallbacksIterAndEach.Select((Callback callback) => $$"""
106+
/// <summary>
107+
/// Iterates the <see cref="{{type}}"/> using the provided .{{Generator.GetInvokerName(callback)}} callback.
108+
/// </summary>
109+
/// <param name="callback">The callback.</param>
110+
public {{Generator.GetInvokerReturnType(callback)}} {{Generator.GetInvokerName(callback)}}Job({{Generator.GetCallbackType(callback, i)}} callback)
111+
{
112+
{{Generator.GetTypeName(Type.TypeHelper, i)}}.AssertReferenceTypes({{(Generator.GetCallbackIsUnmanaged(callback) ? "false" : "true")}});
113+
{{Generator.GetTypeName(Type.TypeHelper, i)}}.AssertSparseTypes(Ecs.GetIterableWorld(ref this), {{(Generator.GetCallbackIsIter(callback) ? "false" : "true")}});
114+
{{Generator.GetInvokerReturn(callback)}}Invoker.{{Generator.GetInvokerName(callback)}}Job(ref this, callback);
115+
}
116+
""");
117+
105118
return $$"""
106119
using System;
107120
108121
namespace Flecs.NET.Core;
109122
110123
public unsafe partial struct {{Generator.GetTypeName(type, i)}}
111124
{
112-
{{string.Join(Separator.DoubleNewLine, iterators)}}
125+
{{string.Join(Separator.DoubleNewLine, iterators.Concat(jobIterators))}}
113126
}
114127
""";
115128
}

src/Flecs.NET.Codegen/Generators/Invoker.cs

+57-30
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics.CodeAnalysis;
44
using System.Linq;
55
using Flecs.NET.Codegen.Helpers;
6+
using Type = Flecs.NET.Codegen.Helpers.Type;
67

78
namespace Flecs.NET.Codegen.Generators;
89

@@ -18,8 +19,10 @@ public override void Generate()
1819
AddSource($"Find/Iterator/T{i + 1}.g.cs", GenerateFindIteratorInvokers(i));
1920

2021
// Iterable Invokers
21-
AddSource($"Iter/Iterable/T{i + 1}.g.cs", GenerateIterIterableInvokers(i));
22-
AddSource($"Each/Iterable/T{i + 1}.g.cs", GenerateEachIterableInvokers(i));
22+
AddSource($"Iter/Iterable/T{i + 1}.g.cs", GenerateIterableInvokers(Generator.CallbacksIter, i));
23+
AddSource($"IterJob/Iterable/T{i + 1}.g.cs", GenerateJobIterableInvokers(Generator.CallbacksIter, i));
24+
AddSource($"Each/Iterable/T{i + 1}.g.cs", GenerateIterableInvokers(Generator.CallbacksEach, i));
25+
AddSource($"EachJob/Iterable/T{i + 1}.g.cs", GenerateJobIterableInvokers(Generator.CallbacksEach, i));
2326
AddSource($"Find/Iterable/T{i + 1}.g.cs", GenerateFindIterableInvokers(i));
2427

2528
// Fetch Component Invokers
@@ -222,9 +225,9 @@ private static string GenerateFindInvokerIterators(Callback callback, int i)
222225
return string.Join(Separator.DoubleNewLine, invokerIterators);
223226
}
224227

225-
private static string GenerateIterIterableInvokers(int i)
228+
private static string GenerateIterableInvokers(Callback[] callbacks, int i)
226229
{
227-
IEnumerable<string> invokers = Generator.CallbacksIter.Select((Callback callback) => $$"""
230+
IEnumerable<string> invokers = callbacks.Select((Callback callback) => $$"""
228231
/// <summary>
229232
/// Iterates over an IIterableBase object using the provided .{{Generator.GetInvokerName(callback)}} callback.
230233
/// </summary>
@@ -254,22 +257,29 @@ public static unsafe partial class Invoker
254257
""";
255258
}
256259

257-
private static string GenerateEachIterableInvokers(int i)
260+
private static string GenerateFindIterableInvokers(int i)
258261
{
259-
IEnumerable<string> invokers = Generator.CallbacksEach.Select((Callback callback) => $$"""
262+
IEnumerable<string> invokers = Generator.CallbacksFind.Select((Callback callback) => $$"""
260263
/// <summary>
261264
/// Iterates over an IIterableBase object using the provided .{{Generator.GetInvokerName(callback)}} callback.
262265
/// </summary>
263266
/// <param name="iterable">The iterable object.</param>
264267
/// <param name="callback">The callback.</param>
265268
/// <typeparam name="T">The iterable type.</typeparam>
266269
/// {{Generator.XmlTypeParameters[i]}}
267-
public static void {{Generator.GetInvokerName(callback)}}<T, {{Generator.TypeParameters[i]}}>(ref T iterable, {{Generator.GetCallbackType(callback, i)}} callback)
270+
public static Entity {{Generator.GetInvokerName(callback)}}<T, {{Generator.TypeParameters[i]}}>(ref T iterable, {{Generator.GetCallbackType(callback, i)}} callback)
268271
where T : unmanaged, IIterableBase
269272
{
273+
Entity result = default;
274+
270275
ecs_iter_t iter = iterable.GetIter();
271-
while (iterable.GetNext(&iter))
272-
{{Generator.GetInvokerName(callback)}}(&iter, callback);
276+
while (result == 0 && iterable.GetNext(&iter))
277+
result = {{Generator.GetInvokerName(callback)}}(&iter, callback);
278+
279+
if (result != 0)
280+
ecs_iter_fini(&iter);
281+
282+
return result;
273283
}
274284
""");
275285

@@ -286,34 +296,51 @@ public static unsafe partial class Invoker
286296
""";
287297
}
288298

289-
private static string GenerateFindIterableInvokers(int i)
299+
private static string GenerateJobIterableInvokers(Callback[] callbacks, int i)
290300
{
291-
IEnumerable<string> invokers = Generator.CallbacksFind.Select((Callback callback) => $$"""
292-
/// <summary>
293-
/// Iterates over an IIterableBase object using the provided .{{Generator.GetInvokerName(callback)}} callback.
294-
/// </summary>
295-
/// <param name="iterable">The iterable object.</param>
296-
/// <param name="callback">The callback.</param>
297-
/// <typeparam name="T">The iterable type.</typeparam>
298-
/// {{Generator.XmlTypeParameters[i]}}
299-
public static Entity {{Generator.GetInvokerName(callback)}}<T, {{Generator.TypeParameters[i]}}>(ref T iterable, {{Generator.GetCallbackType(callback, i)}} callback)
300-
where T : unmanaged, IIterableBase
301+
IEnumerable<string> invokers = callbacks.Select((Callback callback) => $$"""
302+
public static void {{Generator.GetInvokerName(callback)}}Job<T, {{Generator.TypeParameters[i]}}>(ref T iterable, {{Generator.GetCallbackType(callback, i)}} callback) where T : unmanaged, IIterableBase
301303
{
302-
Entity result = default;
303-
304-
ecs_iter_t iter = iterable.GetIter();
305-
while (result == 0 && iterable.GetNext(&iter))
306-
result = {{Generator.GetInvokerName(callback)}}(&iter, callback);
307-
308-
if (result != 0)
309-
ecs_iter_fini(&iter);
310-
311-
return result;
304+
World world = iterable.World;
305+
306+
Ecs.Assert(!world.IsDeferred() && !world.IsReadOnly(), "Cannot run multi-threaded query when world is already in deferred or readonly mode.");
307+
308+
ecs_readonly_begin(world, true);
309+
310+
int stageCount = world.GetStageCount();
311+
312+
using CountdownEvent countdown = new(stageCount);
313+
314+
for (int i = 0; i < stageCount; i++)
315+
{
316+
ThreadPool.QueueUserWorkItem(Work, new {{Generator.GetTypeName(Type.WorkerState, i)}}.{{callback}}
317+
{
318+
Countdown = countdown,
319+
Worker = new {{Generator.GetTypeName(Type.WorkerIterable, i)}}(iterable.GetIter(world.GetStage(i)), i, stageCount),
320+
Callback = callback
321+
}, true);
322+
}
323+
324+
countdown.Wait();
325+
326+
ecs_readonly_end(world);
327+
328+
return;
329+
330+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
331+
static void Work({{Generator.GetTypeName(Type.WorkerState, i)}}.{{callback}} state)
332+
{
333+
state.Worker.{{Generator.GetInvokerName(callback)}}(state.Callback);
334+
state.Countdown.Signal();
335+
}
312336
}
313337
""");
314338

315339
return $$"""
316340
using System;
341+
using System.Runtime.CompilerServices;
342+
using System.Threading;
343+
317344
using static Flecs.NET.Bindings.flecs;
318345
319346
namespace Flecs.NET.Core;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Flecs.NET.Codegen.Helpers;
4+
5+
namespace Flecs.NET.Codegen.Generators;
6+
7+
public class WorkerState : GeneratorBase
8+
{
9+
public override void Generate()
10+
{
11+
for (int i = 0; i < Generator.GenericCount; i++)
12+
{
13+
AddSource($"T{i}.g.cs", GenerateWorkerStateStructs(i));
14+
}
15+
}
16+
17+
public static string GenerateWorkerStateStructs(int i)
18+
{
19+
IEnumerable<string> structs = Generator.CallbacksRunAndIterAndEach.Select((Callback callback) => $$"""
20+
public struct {{callback}}
21+
{
22+
public CountdownEvent Countdown;
23+
public {{Generator.GetTypeName(Type.WorkerIterable, i)}} Worker;
24+
public {{Generator.GetCallbackType(callback, i)}} Callback;
25+
}
26+
""");
27+
28+
return $$"""
29+
using System;
30+
using System.Threading;
31+
32+
namespace Flecs.NET.Core;
33+
34+
public static unsafe partial class {{Generator.GetTypeName(Type.WorkerState, i)}}
35+
{
36+
{{string.Join(Separator.DoubleNewLine, structs)}}
37+
}
38+
""";
39+
}
40+
}

src/Flecs.NET.Codegen/Helpers/Type.cs

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public enum Type
2424
SystemBuilder,
2525
TimerEntity,
2626
TypeHelper,
27+
WorkerState,
2728
UntypedComponent,
2829
WorkerIterable
2930
}

src/Flecs.NET.Codegen/Program.cs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
Generate<TypeHelper>();
2424
Generate<UntypedComponent>();
2525
Generate<WorkerIterable>();
26+
Generate<WorkerState>();
2627
Generate<World>();
2728

2829
return;

src/Flecs.NET.Tests/CSharp/Core/QueryTests.cs

+52
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
using System;
2+
using System.Collections.Generic;
23
using Flecs.NET.Core;
34
using Xunit;
45

56
namespace Flecs.NET.Tests.CSharp.Core;
67

78
public unsafe class QueryTests
89
{
10+
private enum CallbackKind
11+
{
12+
Delegate,
13+
Pointer
14+
}
15+
16+
public static IEnumerable<object[]> TestData =
17+
[
18+
[CallbackKind.Delegate],
19+
[CallbackKind.Pointer],
20+
];
21+
922
[Fact]
1023
private void IterCallbackDelegate()
1124
{
@@ -1012,4 +1025,43 @@ private void GroupByCtx()
10121025

10131026
query.Each((Iter _, int _) => { });
10141027
}
1028+
1029+
[Theory]
1030+
[MemberData(nameof(TestData))]
1031+
private void EachJob(CallbackKind callbackKind)
1032+
{
1033+
using World world = World.Create();
1034+
using Query<Position, Velocity> query = world.Query<Position, Velocity>();
1035+
1036+
world.SetStageCount(Environment.ProcessorCount);
1037+
1038+
Assert.Equal(Environment.ProcessorCount, world.GetStageCount());
1039+
1040+
Span<Entity> entities = stackalloc Entity[1000];
1041+
1042+
for (int i = 0; i < 1000; i++)
1043+
{
1044+
entities[i] = world.Entity()
1045+
.Set(new Position(i, 0))
1046+
.Set(new Velocity(0, i));
1047+
1048+
if (int.IsEvenInteger(i))
1049+
entities[i].Set(new Mass(i));
1050+
}
1051+
1052+
if (callbackKind == CallbackKind.Delegate)
1053+
query.EachJob(Callback);
1054+
else
1055+
query.EachJob(&Callback);
1056+
1057+
for (int i = 0; i < 1000; i++)
1058+
Assert.Equal(new Position(i, i), entities[i].Get<Position>());
1059+
1060+
return;
1061+
1062+
static void Callback(Entity e, ref Position p, ref Velocity v)
1063+
{
1064+
e.Set(new Position(p.X + v.X, p.Y + v.Y));
1065+
}
1066+
}
10151067
}

0 commit comments

Comments
 (0)