-
-
Notifications
You must be signed in to change notification settings - Fork 45
feat: Use the Handles, Luke! #427
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
base: main
Are you sure you want to change the base?
Conversation
I got the tests compiling and running. There are 9 failures with features 'lua' and 'rhai'. |
There is a regression in that it doesn't support multiple scripts. I'm trying to get that ready in the 'use-entities' branch, which is compiling now but not running yet if you wanted to take a peek. But maybe this PR needs to cook a little more. I'm gonna take another pass on it tonight. |
Ok, I have fixed the regression. Game of life works in all ways for Lua. For Rhai it doesn't seem to work for static scripts. So as I was trying to make scripts work with a shared context and per entity/script, I realized the split of contexts is actually a big policy decision. I imagine in most cases it'll come down to a few qualities: entity, script, and another thing I'm calling domain. To that I end I wrote this trait: /// A generic script context provider
pub trait ScriptContextProvider<P: IntoScriptPluginParams> {
/// Get the context.
fn get(&self, id: Option<Entity>, script_id: &ScriptId, domain: &Domain) -> Option<&Arc<Mutex<P::C>>>;
/// Insert a context.
fn insert(&mut self, id: Option<Entity>, script_id: &ScriptId, domain: &Domain, context: P::C) -> Result<(), P::C>;
/// Returns true if there is a context.
fn contains(&self, id: Option<Entity>, script_id: &ScriptId, domain: &Domain) -> bool;
} My hope is this would allow us to provide the right criteria to choose contexts divisions. SharedContextSo the shared context is implemented with this: pub struct SharedContext<P: IntoScriptPluginParams>(pub Option<Arc<Mutex<P::C>>>); and it ignores the arguments and just provides the same context to everyone. EntityContextA per entity context is implemented like this: /// Stores the script context by entity.
pub struct EntityContext<P: IntoScriptPluginParams>(HashMap<Entity, Arc<Mutex<P::C>>>); ScriptContext compositionAnd the resource we make available is a composition: #[derive(Resource)]
/// Keeps track of contexts
pub enum ScriptContext<P: IntoScriptPluginParams> {
/// One shared script context
Shared(SharedContext<P>),
/// One script context per entity
///
/// Stores context by entity with a shared context as a last resort when no
/// entity is provided.
Entity(EntityContext<P>, SharedContext<P>)
} And if we wanted to we could other provide other policies like by ScriptId or something. DomainBut what's domain? It's the old /// A kind of catch all type for script context selection
///
/// I believe this is what the original ScriptId was intended to be.
pub type Domain = Option<Cow<'static, str>>; I don't do anything with My vision for domain was to allow users to clump scripts together a little more haphazardly. Something like the following fantasy code would put scripts into one of two domains: asset_server.load_with_settings("player.lua", |settings: &mut ScriptSettings| {
settings.domain = Some("player".into());
});
asset_server.load_with_settings("monster.lua", |settings: &mut ScriptSettings| {
settings.domain = Some("env".into());
});
asset_server.load_with_settings("effects.lua", |settings: &mut ScriptSettings| {
settings.domain = Some("player".into());
}); Take awayThis code is working and I think it's ready for your attention. Having spent more time with this code base I'm in even more awe of what you've put together. Let me know if there's anything I can do to help. I'm sorry that this branch is probably going to conflict with 0.16 branch. :( Further workI'm going to try to make this branch work with Nano-9 to dog food it in the coming days. |
It was quick and painless to switch over to this branch in Nano-9. See the commit. |
DomainsI went ahead and fleshed out the pub type Domain = Cow<'static, str>; How to select a domain?I thought about adding a 'domain' field to /// Defines the domain of a script
#[derive(Component)]
pub struct ScriptDomain(pub Domain); Static scriptsI could allow for static scripts to have a domain but I have left them all as being in no domain. Are static scripts grouped into their own context? Are static scripts a kind of domain? Are they superseded by domains? Add support for context per scriptI added impl<P: IntoScriptPluginParams> ScriptContext<P> {
/// Use a shared script context
pub fn shared() -> Self {
Self::Shared(SharedContext::default())
}
/// Domain contexts or a shared context
pub fn with_domains() -> Self {
Self::Domain(DomainContext::default(), SharedContext::default())
}
/// Use one script context per entity
pub fn per_entity() -> Self {
Self::Entity(EntityContext::default(), SharedContext::default())
}
/// Use one script context per entity
pub fn per_script() -> Self {
Self::ScriptId(ScriptIdContext::default())
}
} Now that there are 4 choices, I'd suggest we change the app.insert_resource(ScriptContext::shared()); Problem: N scripts produce N callback evaluations even with 1 shared contextI added a Proposal: Evaluate Callbacks on a Per Context BasisHere is my take for least-surprise: If I have five scripts all in one shared context, and I fire a call to If I have four scripts in three different contexts, and I fire a call to To facilitate that, I added the following method: /// A generic script context provider
pub trait ScriptContextProvider<P: IntoScriptPluginParams> {
// ...
/// Hash for context.
///
/// Useful for tracking what context will be returned by `get()` without
/// requiring that `P::C` impl `Hash` and cheaper too.
fn hash(&self, id: Option<Entity>, script_id: &ScriptId, domain: &Option<Domain>) -> Option<u64>;
} What I think is nice about this is it works the same as before if you're placing every script in its own context. But it will also work with scripts, entities, domains, and shared contexts. Docs?Would you like some help updating the docs/book for this PR? |
I'm prepping this branch for a BMS 0.14.0. Expecting some real-world use I updated the
And its default is I found some tests failing (and some not even compiling--eek). I'll try to fix them tomorrow. |
I keep thinking I'm close to being done, but then cargo unveils some new slew of errors. After much teeth gnashing, I have all 172 tests passing when I run "cargo test" from the root workspace. However, when I go into the core crate's directory and run "cargo test" I get compilation errors from the tests. Shouldn't cargo run all the workspace crates' tests? Anyway, fixing the test harness unveiled this bug:
To fit better with I didn't seek out to do this, I think I was bewildered by the test harness not working, but as I was patching things up, I realized that we could load scripts without cloning their contents if they're given via a Will continue to work on this until I get all tests to pass. Do you have a magic command you provide on the command line to run all the tests? |
try |
Progress but |
It's all compiling now. 3 tests are failing. Unfortunately the fix seems a little hairy. The called_on_right_recipients test in particular requires a change in the event handler to fix. My workday has been interrupted quite a bit, so I put my partial commits into my "use-handles-dev" branch so it's not distracting in this PR. I changed the event handler and now 5 tests are failing. On it goes. |
All tests pass! The game of life example starts, stops with Lua & Rhai with and without static argument. Assumption BreakageI had been adding pub struct ContextKey {
/// Entity if there is one.
pub entity: Option<Entity>,
/// Script ID if there is one.
pub script_id: Option<Handle<ScriptAsset>>,
/// Domain if there is one.
pub domain: Option<Domain>,
} It's the new key to context. /// A generic script context provider
pub trait ScriptContextProvider<P: IntoScriptPluginParams> {
/// Get the context.
+ fn get(&self, context_key: &ContextKey) -> Option<&Arc<Mutex<P::C>>>;
- fn get(&self, id: Option<Entity>, script_id: &ScriptId, domain: &Option<Domain>) -> Option<&Arc<Mutex<P::C>>>;
// ...
} What's in a Domain?With the inclusion of /// A kind of catch all type for script context selection
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct Domain(u64);
impl Domain {
/// Create a domain handle.
pub fn new(hashable: impl Hash) -> Self {
Domain(DefaultHashBuilder::default().hash_one(hashable))
}
} New is OldThis is the new default DomainEntityScriptId(Or<DomainContext<P>, Or<EntityScriptIdContext<P>, Or<ScriptIdContext<P>, SharedContext<P>>>>), You can read the
The prior version had behaviors 2 and 3 and 4 as a separate assignment mode. This adds domains without interfering with the rest if they're not used. And its easy to design different behaviors within the library. For instance, if we wanted to have all static scripts live in the same context we can drop the DomainX(Or<DomainContext<P>, Or<EntityScriptIdContext<P>, SharedContext<P>>>), This means Reify me notI did a big refactor on the It now has these benefits:
ApologiesI am sorry that this PR has gotten so big. At the beginning it seemed like this could be a less invasive set of changes. Having failed to make this change set small, I have endeavored to make it good. |
I updated the BMS book's chapter on contexts and managing scripts. I realized through the original documentation that I had misunderstood BMS's original script context isolation. I thought it somehow isolated scripts on an entity-script pair basis. I wrote the entity-script pair context so it would be a drop-in substitute for the original system. Oops. Turns out isolating based on script would be the drop-in replacement. Choose
|
Ello, apologies for not interacting much yet, rest assured I am observing the branch and trying to grok it (and I like this direction), I've now gotten the bevy migration branch to a final state, so now I am going to be reviewing the changes in this PR over the next week or so, do you mind merging the last bit of conflicts (as I need the xtask changes to load the workspace locally). for the merge order, now I am thinking:
thoughts? |
This is a combination of 68 commits. hack: It compiles. hack: Compile with lua54 feature. refactor: Accept Handle<ScriptAsset> in ScriptComponents. feature: Handle static scripts. bug: Fix rhea and static script unloading. feature: Add ScriptSetttings to loader. This introduces a serde dependency though. :( test: Prep tests to run. feature: Add DisplayProxy. Refactor continues. refactor: Bring test harness in line. test: Tests are running! test: Make test runnable without rhai. excise: Remove ScriptMetadata and ScriptEvent. We don't need them since asset's know their language. excise: Drop Script::asset field. It's a duplicate with Script::id now. chore: fix build problem in xtask perf: Can create_or_update_script w/o clone. feature: Try to use the entities. hack: It compiles but idk why. I fought with HandlerContext trying to add a Query to it for the longest time. hack: Compiles but doesn't run well. Log is flooded with these two lines: ```log 2025-06-28T11:46:54.321715Z ERROR bevy_mod_scripting_core::handler: Rhai: Failed to query entities with scripts: Cannot claim access to base type: Global. The base is already claimed by something else in a way which prevents safe access. Location: "/Users/shane/Projects/bevy_mod_scripting/crates/bevy_mod_scripting_core/src/extractors.rs:337". Context: Could not claim exclusive world access 2025-06-28T11:46:54.322046Z ERROR bevy_mod_scripting_core::handler: Lua: Failed to query entities with scripts: Cannot claim access to base type: Global. The base is already claimed by something else in a way which prevents safe access. Location: "/Users/shane/Projects/bevy_mod_scripting/crates/bevy_mod_scripting_core/src/extractors.rs:337". Context: Could not claim exclusive world access ``` feature: Use SharedContext(P::C) for global context. feature: GOL works with SharedContext. feature: Works with ScriptContextProvider trait. refactor: It's all coming together now. bug: Make per-entity work with static scripts. doc: Clarify EntityContext. Hide internal struct. chore: fix new clippy warnings causing build errors (makspll#428) partial: Add domain handling. feature: Add ScriptContextProvider::hash member. feature: Call labels on a per-context basis. doc: Explain call per-context rationale. feature: More ScriptComponentProvider<P> variants. chore: Restore commented log plugin. test: Remove script_id_generator parameter. test: Remove metadata tests. refactor: Prefer Option<Entity> vs Entity::from_raw(0). bug: Scripts w/o AssetServer didn't work. Now you can add scripts via `Assets<ScriptAsset>` or the `AssetServer`. test: Fix tests. perf: Avoid cloning script content when possible. doc: Document script::Or. refactor: Unscore unused parameters in context providers. chore: Fix it, Clippy! doc: Add note about ScriptContext::hash(). test: Fix/refactor tests. feature: Remove todo! & impl supported extensions. Implement the ConfigureScriptAssetSettings again. chore: Make core tests compile. test: It compiles! 8 test failures. test: All tests passing except 3. partial: Handle event rework. feature: Add iter() to ScriptContextProvider<P>. refactor: Rework event handling. partial: Collecting contexts. partial: More handler event mulling. feature: New event handler. Early exit. Fewer allocations. No querying unless necessary. feature: Add remove() to ScriptContextProvider<P>. feature: Make DeleteScript delete contexts too. feature: Add EntityScriptIdContext and ContextKey. feature: Rename iter() to values(). feature: Add iter() to ScriptContextProvider<P>. feature: Handle Recipient::ScriptId fully. Static scripts included. Fewer clones. refactor: Move ContextKey to its own file. feature: Made Domain(u64). refactor: Weave ContextKey in. It'd be nice if ContextKey had a Handle<ScriptAsset> just for logging purposes. refactor: Use ContextKey. Tests pass! refactor: Use ContextKey instead of ScriptId. chore: Clean up warnings. bug: Fix game of life. feature: Add prelude. I like explicit imports, but I'm trying to make the code in mdbook runnable, just so it is easier to maintain, and it feels like it'd be a mess without a prelude. doc: Update book. A few code refactors to make book writing easier. feature: Add ConfigureScriptAssetSettings to prelude.
Great to hear! I think the versions sound fine. A handles+bevy0.15 as an alpha is good by me, better even than polluting the version space with use-handles and not.
I think the desired end is good, but I worry the process of rebasing this branch onto a 0.16 will be a chore. But maybe it won't. To update this branch to "main," I'm going to squash everything in this branch just to make the rebasing easier. Maybe it won't be as troublesome if the commits are fewer, but I was initially thinking I'd take the patch from the old handles+0.16 and apply that on top of this branch with whatever fix up was required. |
Reload and unload don't seem to be working yet.
I squashed this branch and rebased it to main. It's compiling. There are two tests which are failing.
And I'm not seeing the unload and reload work yet. Probably need to add a test for those. |
Ah yeah sorry by rebase I mean rebase OR merge, don't mind which, squash and rebase is also good! Folks at work treat those words interchangably and it's infected me |
Nice, I will pull this down locally and play with it |
Fixed the failing assignment tests. All tests are passing! I added an example called "run-script" to exercise the reload functionality. It's a mouthful to run reload.lua because of the needed features: cargo run --features lua54,bevy/file_watcher,bevy/multi_threaded --example run-script -- reload.lua I know you're looking to get more examples, so I figured this might be good to have for the rust part and maybe putting together quick pure lua or rhai examples as well. Reload functionality is not working yet in this branch. |
Reload functionality is now working! Asset Handling Behavior ChangeI forgot to mention this. But we've been using TODO
|
I was only planning to file issue #426 tonight, which states the problems I was having. I thought if I poked around I'd find the implementation reason for why what I suggest there wouldn't work. So I made a branch and rolled up my sleeves, and I was able to pretty quickly get exactly what I wanted.
Handles
In the issue I suggest doing away with
ScriptId
. As I worked with the implementation, it became clear I actually wanted to redefine it.Beyond that I made it so
ScriptComponent
holdsHandle<ScriptAsset>
s. The nice thing about this is you can use this pattern:No need to store a strong handle somewhere so your script isn't unloaded. However, if you rely on that behavior you can still do that like you do with any other asset-based item using weak handles.
This preserves the semantics of the current release.
ScriptAsset loading versus evaluation
In this PR the static scripts are
handled essentially the same as before except theycan retain handles (both strong and weak). EDIT: A script may be loaded without evaluation. If it is added as a static script, it will then be evaluated.The non-static scripts are not evaluated until they are added to a
ScriptComponent
. Then they are evaluated in order, sequentially.ScriptAsset change
I suggested a change to
ScriptAsset
and that survived this refactoring.I also provided a
ScriptSettings
where one can provide a definite language.None
will use the file's extension.One downside about
ScriptSettings
is it required adding "serde" with the "deriv" feature as a dependency.Example
I updated the Game of Life to work with Lua and Rhai and all its console options.
Remaining work
I have not tried to exercise any other languages or even do much with the tests. But I'd be happy to move this forward to handle the rest with some consensus that that's the right direction.