-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic API For Use With Scripting #96
Comments
Hi 🙋 This was discussed a long time ago on gitter but the api changed a lot since then. At the time, a few things were implemented:
It was a lot of work and nobody needed it so the systems fn got commented out and Making shipyard use I don't think I'll personally need it and there are other features that will benefit more people. |
Cool, thanks for the reply. I might take a look at it and I'll ask questions here if I have any. |
Apparently I choose the wrong day to not look at the forum 😆 Here's what I had in mind, I haven't tested it so there might be some incorrect or missing things. First, since FFI types are all going to come in the shape of byte arrays, we can store them in a special When users want to create a FFI storage they'll have to call a method that takes:
Then to retrieve the data, returning one type, two types or an enum is a trade-off.
I think enum is the best bet. For the views themselves, there are a few ways to go about it. I think the simplest one would be to make the storage carry its lock. This way we can make the view a simple pointer to the storage and have all the information needed. Views would just be something like:
The storage will stay borrowed until a For lots of methods, shipyard uses tuples of various sizes and generics. We can't use this in FFI but I think variadic functions can have the same role. I probably forgot a ton of things. What are your thoughts? |
Well, it started out as a "Can you do I just pushed my code and here is the direction I was going: https://github.com/leudz/shipyard/pull/99/files#diff-5c4fb3ad249d34ee03fab6185b3aef4aR12 I haven't had time to process your ideas, but I'll leave my thoughts as soon as I can! 😄 |
So I've gotten some time to look at this and I have some questions/thoughts. Note that this new idea is a little different than the one in the PR currently. Separation of FFI and Dynamic Systems IssuesSo, while I'm not 100% sure this is the best idea, I would propose that we separate the issues FFI and dynamic systems into separate problems to solve individually, starting with dynamic systems. What I mean is that while we will definitely want an FFI, currently I actually don't need one ( yet ), and I think it would be nice to start by providing a way from pure Rust to create systems and components that are only known at runtime, without having to use constructs such as unsafe pointers that would be required for FFI. Dynamic systems is something that is useful in plain Rust, even without an FFI. ( For instance, I want to bind the ECS to the RustPython implementation, which has a Rust API so I don't have to use any FFI ). After we have established a Rusty way to create dynamic systems we could add an FFI. Component StorageSo, normally we store components in the I didn't understand what you meant when you said
Yeah, I agree. I think we might be able to do something like this playground example where we just build a ViewsAt this point views may hardly need to change? If we use a I think this design lets us keep almost every aspect about the way everything is already designed. The workloads and system scheduler should be able to remain unchanged. I haven't finished combing through the code-base yet, though so I could be horribly wrong about all this. I don't understand it enough yet to know whether or not it will work until I try to put it all down and see if |
Yeah, I'm not sure the whole I'm really not the most experienced with strategies for this kind of thing. Is there any way to store some sort of special type inside of a Or does the |
Sorry it took me so long to respond, I knew someone on zulip used Python with shipyard so I asked them a few questions. I also looked into pyo3 and a few other things. My few questions yielded interesting answers. Assuming you really need scripting, I agree with you that you probably don't always need the full FFI experience. So first level of dynamism: custom storages. Second level: fully dynamic storages. Here's what custom storages would look like: struct MyStorage(Vec<usize>);
impl UnknownStorage for MyStorage {
// -- snip --
}
let world = World::new();
world.add_custom(0, MyStorage(Vec::new()));
world.add_custom(1, SparseSet::<[u8; 8]>::new());
let my_storage: CustomView<MyStorage> = world
.borrow_custom(0)
.map(|storage| storage.as_any().downcast_ref().unwrap());
For workloads the syntax isn't final, but users will have to give this kind of info: fn sys1(world: &World) -> Result<(), error::Run> {
let my_storage: CustomView<MyStorage> = world
.borrow_custom(0)
.map(|storage| storage.as_any().downcast_ref().unwrap());
Ok(())
}
fn sys1_infos(infos: &mut BorrowInfos) -> (bool, bool) {
infos.extend_from_slice(&[(0, Mutation::Exclusive), (1, Mutation::Shared)]);
(true, true)
}
world
.add_workload("...")
.with_custom_system(sys1, sys1_infos)
.build(); What do you think of custom storages and systems? Do you think you'll need the second level? |
No problem. :)
I've done a little looking into Mun, and it looks great, but I think we can enable great hot-reloading in a language agnostic way with some extra logic around the scripted ECS. That way you could have hot reloading by dynamically reloading systems/components for any programming language that you bind to the ECS. I think. :) +1 for custom storages, that sounds like the right approach.
Would your example count as "Second level"? I'm not sure I understood the difference. If we can implement custom storages by implementing I think that we will need to build something that uses either I would think that a built-in custom component storage ( like a byte-array based one or something ) could be useful, or maybe provided in a different crate/example, but the important part I think is just being able to add
That looks pretty good to me. |
"Level 2" will be built on top of "level 1". And yes in theory users could make it but it'll involve a lot of If yes then a custom We can't use |
Ah, OK, then yes, "Level 2" would be required. The idea is to have something like this schema definition example ( which looks essentially like Rust ) that would describe the data for each component. These would be loaded at runtime and the byte representation of the component would be determined from the schema, potentially by using the Each different scripting language integration would need to have some implementation that takes the byte representation of the component and the associated schema file, and produces a native object or otherwise some native interface to represent the component in the target scripting language. For example, for Python, you would need something to translate the raw bytes into a Python Object. That Object would be manipulated by the Python script and those manipulations would be translated into modifications of the raw bytes stored in the ECS. This you would do for each scripting language. Because you standardize on the byte representation and schema definition, you provide an interface for any number of scripting languages to operate on the same components in the same ECS world seamlessly! I'm glad to help with this however I can and can find time for ( with my time being pretty inconsistent, just a warning 😄 ). I'm not super acquainted with |
It's an interesting project, very ambitious. I'm going to check what needs to be done for "level 1", if it's not much work, I'll do it. If it requires more time I'll post a comment explaining what needs to be done and how. Regardless of what I do, you'll get updates here. |
Hey @leudz is there anything I can help with yet? Might get some time over the weekend for this. Don't worry if you haven't gotten to anything yet. 🙂 |
Hey! I don't think you can help yet but I've made some progress on what I have to do before taking care of this issue. |
Friendly ping. :) Any progress or help I can give? |
With the release of the Bevy game engine it looks like I'm going to be building on that for my game engine and therefore probably won't need this anymore. I'm really sorry if that makes any time you spent on this a huge waste. 😬 Thanks for the useful discussion, though. 👍 😃 |
No waste of time, don't worry. Best of luck for your project =) |
Hey there, I'm considering building a game engine on top of shipyard, but scripting support is essential. What I think I need is an API to shipyard that doesn't require the use of Rust's fancy type checking. I'm not 100% sure I know what I need to accomplish what I need to accomplish, but I want to start the discussion to start figuring it out. 🙂
Firstly, don't get me wrong, I love Rust's fancy type system and, though I haven't use shipyard extensively yet, I think I like the direction that you have been going with in terms of ergonomics. The problem, though is that I need to be able to insert new components and systems that are not known at compile time. I'm looking to accomplish something similar to Amethyst's Scripting RFC ( you don't necessarily need to read it, I'll explain ).
So the goal is to have an API, potentially accessible over a C FFI, though not necessary for my use-case yet ( and maybe that FFI could be a separate crate? ), that we can use to store components, which will be opaque byte arrays. The component data will likely be stored in the C memory layout, but that is not important to the ECS itself it just needs to store the bytes.
I need to be able to register components at run-time, though and so I need a way to distinguish between all of the different component types, even though they are all byte-arrays. In other words, we can't use the Rust type system to identify components. We might need to identify components by strings or integers or otherwise have some sort of ComponentID.
I also need to be able to query the components in systems that are created at runtime. I think workloads would need to be defined at runtime, too.
I don't know anything about the internals of shipyard yet, and I may not have explained that very well, but here's the gist:
I might be able to help with this, but no promises. I'll probably be checking out the internals of shipyard a bit to see how it works and how easy it might be to implement.
The text was updated successfully, but these errors were encountered: