Replies: 2 comments 4 replies
-
As far as I can tell, we need a QueryData or QueryFilter (don't know enough about the inner workings of bevy to make an informed descision here) to implement SystemParam, that way on a per dynamic query basis we can register a system that uses it. Maybe something like: fn system(query: QueryState<FilteredEntityMut>) {
} Where we can manually register the system, passing in the dynamic query so that the scheduler knows what it can/can't run in parallel. let mut system = IntoSystem::into_system(system);
system.bind_query(QueryBuilder::<FilteredEntityMut>::new(&mut (*world)).ref_id(ComponentId::new(some non rust component
system.initialize(&mut (*world)); It would be good if I can just register this same function as a system multiple times binding differen't queries so that I have a SystemId I can pass back to C# to know which function pointer we are invoking. I have also refined the C# API to look much more like the Rust API, still need to swap query params to tuples so that the query syntax is exactly the same but it's getting there: var app = new App();
var world = app.World;
world.RegisterType<Example1>();
world.RegisterType<Example2>();
world.RegisterType<Example3>();
world.Spawn((
new Example1 { A = 0, B = 1, C = 2 },
new Example2 { D = 3, E = 4, F = 5, G = 6 }
));
world.Spawn((
new Example1 { A = 7, B = 8, C = 9 },
new Example2 { D = 10, E = 11, F = 12, G = 13 }
));
world.Spawn((
new Example1 { A = 14, B = 15, C = 16 },
new Example2 { D = 17, E = 18, F = 19, G = 20 },
new Example3 { H = 21, I = 22, J = 23 }
));
TestSystem1.Run(new Query<Example1, Mut<Example2>>(world));
TestSystem2.Run(new Query<Example1, Example2>(world));
[Component]
public partial record struct Example1(int A, int B, int C);
[Component]
public partial record struct Example3(int H, int I, int J);
[Component]
public partial record struct Example2(int D, int E, int F, int G);
[System]
public struct TestSystem1
{
public static void Run(Query<Example1, Mut<Example2>> query)
{
query.ForEach((Example1 example1, ref Example2 example2) =>
{
Console.WriteLine($"Example1: A: {example1.A}, B: {example1.B}, C: {example1.C}");
Console.WriteLine($"Example2: D: {example2.D}, E: {example2.E}, F: {example2.F}, G: {example2.G}");
example2.D = 100;
Console.WriteLine("----------------------");
});
// Iterating a 2nd time to show mutation works
query.ForEach((Example1 example1, ref Example2 example2) =>
{
Console.WriteLine($"Example1: A: {example1.A}, B: {example1.B}, C: {example1.C}");
Console.WriteLine($"Example2: D: {example2.D}, E: {example2.E}, F: {example2.F}, G: {example2.G}");
Console.WriteLine("----------------------");
});
}
}
[System]
public struct TestSystem2
{
public static void Run(Query<Example1, Example2> query)
{
query.ForEach((example1, example2) =>
{
Console.WriteLine($"Example1: A: {example1.A}, B: {example1.B}, C: {example1.C}");
Console.WriteLine($"Example2: D: {example2.D}, E: {example2.E}, F: {example2.F}, G: {example2.G}");
Console.WriteLine("----------------------");
});
}
} There is a lot of code gen magic to make this work, namely the Query needs some way to define that the access you want to a component is mutable, to do this I have a public interface Mut<T> : IComponent<T>
{
static nuint IComponent.GetId(Dictionary<string, nuint> components) => components[typeof(T).Name];
static unsafe ref T IComponent<T>.Get(nuint** components, int index, int step, int offset) => ref Unsafe.AsRef<T>(components[index * step + offset]);
} This allows me to take some T that implements IComponent (both user components and Mut do) and just statically call Per system I generate extensions for the Query type with a custom delegate that allows me to define which generic params are mutable. The generated code for the above example looks like this: For everything with the [StructLayout(LayoutKind.Sequential)]
public partial record struct Example1 : IComponent<Example1>
{
public static nuint GetId(Dictionary<string, nuint> components)
{
return components[nameof(Example1)];
}
public static unsafe ref Example1 Get(nuint** components, int index, int step, int offset)
{
return ref Unsafe.AsRef<Example1>(components[index * step + offset]);
}
} For everything with the public static partial class TestSystem1Extensions
{
public delegate void QueryExample1MutExample2Action<T1, T2>(T1 t1, ref T2 t2);
public static void ForEach(this Query<Example1, Mut<Example2>> query, QueryExample1MutExample2Action<Example1, Example2> action)
{
for (var i = 0; i < query.Count; i++)
action(query.Get<Example1>(i, 0), ref query.Get<Example2>(i, 1));
}
} There are also questions around passing data to the asset server, obviously in rust you have the assets directory that gets packaged up with the binary. The same is possible in C# so is it possible for me to pass the bytes encoded into the C# binary over FFI to rust to load at runtime? If so, what might that look like? |
Beta Was this translation helpful? Give feedback.
-
Can you post this code on github? This may be useful for me. |
Beta Was this translation helpful? Give feedback.
-
I am creating bindings to bevy for C#, there have been some challenges with C#'s lack of flexibility with generic constraints but I have settled on something that seems reasonable. I was able to get started following this example: https://github.com/bevyengine/bevy/blob/main/examples/ecs/dynamic.rs.
So far the C# code looks like this:
There will likely be some changes to make getting mutable references out more ergonomic but this works for now. I haven't been able to find any information on runtime dynamic systems/schedules so I'm unsure where to go next, would the idea be that I just create a rust system that runs in each schedule to call a function pointer into my library and build out the API on the C# side or is there a runtime API I haven't come across?
For a bit of context on how things are currently working, I register the types in bevy and use their ID's to construct dynamic queries, then just pass the pointers over to C# to interpret as the correct types which gives me a query I can iterate over. With this I could technically build up systems in C# but I would rather not worry about the parallel mutability concerns if possible.
And ideas would be appreciated.
Thanks
Beta Was this translation helpful? Give feedback.
All reactions