#bevy_mod_scripting
1 messages · Page 2 of 1
Callback for onStartup and such?
The way I'm setting things up is all the logic/game will all be rust based, and scripting are ways to insert things or change things.
As of now I don't have enough experience with rust/bevy/BMS to exactly say what I need or want unfortunately.
And even if I did I wouldn't want to place expectations considering this is mainly technology evaluation and feasability for an escape game project.
For now I feel like what you said seem very sensible.
But maybe an git issue to discuss what would be the most sensible callbacks ?
I am thinking of pipelining the whole script load process so that people can react to things in between all the internal machinery
Because a callback on_script_loaded could trigger a reload of other things.
Library reloads -> all scenario and tooling reload
Although not automatic that could be a way to still have it sandboxed.
perhaps wrap a the "require()" call with a register internally could also be another aproach, but then we hit this...
Anyways. Those are dreams for now and i find the direction you shared to be very sensible.
One step at a time.
id prefer two steps at a time for double the speed 
Ah yes, the stair speedrunners!
Yeah, I'll have to pull out handler resources to be more portable for all of this i think
@old trail Should this be working? world.get_type_by_name("bevy::render::camera::ScalingMode")
I am not sure what the type path looks like but you can check what the type registry prints using this https://github.com/makspll/bevy_mod_scripting/blob/main/crates%2Fbevy_mod_scripting_core%2Fsrc%2Fbindings%2Fworld.rs#L884
Replied to the wrong message
Ok with some print statements i found out it was actually bevy_render::camera::projection::ScalingMode but damn thats fucked up, cus in docs.rs it shows up the first way i had it.
Yeah, I think they follow the internal paths
boa_engine
When I get time I’ll try to make it work for gleam generated js, which is my objective. That being said should I named it bevy_mod_scripting_gleam?
Very cool, that sounds reasonable
I think that would be the first external language crate if you release it
Exciting
Finally got the observers and such working nice now. I'm debating wether to try networking or saving or integration tests next...
@old trail
.add_systems(
FixedUpdate,
(
update_rendered_state.after(sync_window_size),
send_on_update.after(update_rendered_state),
(
event_handler::<OnUpdate, LuaScriptingPlugin>,
#[cfg(feature = "rhai")]
event_handler::<OnUpdate, RhaiScriptingPlugin>,
event_handler::<OnClick, LuaScriptingPlugin>,
#[cfg(feature = "rhai")]
event_handler::<OnClick, RhaiScriptingPlugin>,
)
.after(send_on_update),
),
);
is that right? why rhai appears twice
One is for the OnUpdate event and the other is for OnClick. U need to call that for each event u want to get handled and per language.
If u removed the last one for example and triggered an OnClick, it would only get handled for lua scripts and not rhai scripts.
event_handler::<OnUpdate, LuaScriptingPlugin>,
#[cfg(feature = "rhai")]
event_handler::<OnUpdate, RhaiScriptingPlugin>,
event_handler::<OnClick, LuaScriptingPlugin>,
#[cfg(feature = "rhai")]
event_handler::<OnClick, RhaiScriptingPlugin>,
#[cfg(feature = "gleam")]
event_handler::<OnUpdate, GleamScriptingPlugin>,
event_handler::<OnClick, GleamScriptingPlugin>,
#[cfg(feature = "gleam")]
event_handler::<OnClick, GleamScriptingPlugin>,
so this?
If the idea is that ur trying to add Gleam, then the last one is redundant (event_handler::<OnClick, GleamScriptingPlugin>,) cus ur adding it twice.
(
event_handler::<OnUpdate, LuaScriptingPlugin>,
#[cfg(feature = "rhai")]
event_handler::<OnUpdate, RhaiScriptingPlugin>,
event_handler::<OnClick, LuaScriptingPlugin>,
#[cfg(feature = "rhai")]
event_handler::<OnClick, RhaiScriptingPlugin>,
#[cfg(feature = "gleam")]
event_handler::<OnUpdate, GleamScriptingPlugin>,
event_handler::<OnClick, GleamScriptingPlugin>,
)
like so?
Yup, that looks reasonable.
I just realised the CI was not publishing benchmarks from main because it detected it as a fork 
Lol whatever you are trying to do to fix it has me getting emails. (Which is fine, I just wanted to notify you)
Yep.
ah yeah, I don't think I have control over that, but you can filter out some of them here:
I wish I could auto filter out bots from these
Yeah makes sense!
Thoughts on these PR's? The gist is:
- the concept of loading/reloading contexts, as well as the main handler function for each scripting plugin (think
Lua,Rhai,MyHomemadeLuaPlugin), is set in stone at compile time ( not context initializers, the core ones ) - makes stuff easier to access, don't need world
- cannot edit these things at runtime
- meaning you can't for example take the default
Luaplugin and just replace the handler, you'd need to use a different type with a different implementation - I am thinking that sort of modding is gonna be rare, and it should be easy to replace all occurrences of the default plugin with your own across the codebase (and you'd need to since the resources are keyed by the
IntoScriptPluginParamstype)
https://github.com/makspll/bevy_mod_scripting/pull/455
https://github.com/makspll/bevy_mod_scripting/pull/456
With the limited usage ive done with BMS I like the idea of simplifying these calls.
I'm trying to figure out how to save script data and apply it again.
Currently i've got a very basic example working with bevy_save, they seem to be using MapEntity trait and ReflectMapEntity trait, much like this example : #1172649537098235924 message
This allows to map entities between two states as their ID changes.
As I get a ScriptValue out of a script, I would like for it to be mapped too so when the data is sent back to the script, it would be with the updated reference.
Do you think it would make sense to add the MapEntity and ReflectMapEntity traits to ScriptValue? Or at least just MapEntity?
Also, I was thinking about what behaviour to do for references that are not entities since it likely means they might not be valid after the save/load process.
Okay so as I'm trying to implement the MapEntity trait I realise that it might not be as simple as I thought...
Is there a way to get an Entity reference from a ScriptValue::Reference without world?
ScriptValue::Reference(reflect_reference) => {
// Get an Entity ref for entity mapping
// I want do something like
// let entity: Entity = entity_mapper.map_entity(entity: Entity)
},
Hmmm... Am I misunderstanding how script systems work? I'm passing along system_builder("test_system", script_asset) but it results in a crash:
thread 'main' panicked at crates\bevy_mod_scripting_core\src\script\context_key.rs:7:45:
variant with name Weak does not exist on enum bevy_mod_scripting_core::script::context_key::ScriptAttachment
Unsure if this is a bug or if I should be passing along something else, buuut I'm not seeing where I pass along the ScriptAttachment of the script.
can u show me what ur assigning to script_asset?
function on_script_loaded()
print(script_asset)
local TransformType = world.get_type_by_name("Transform")
local update_schedule = world.get_schedule_by_name("Update")
local system = system_builder("test_system", script_asset)
:query(
world.query()
:component(TransformType)
)
world.add_system(update_schedule,system)
end
function test_system(query_result)
for i,result in query_result do
print(result:components()[1])
end
end```
I'm assuming script_asset isn't the correct thing to pass. I assumed so with the changes to the script_id that it accepts script_asset :P
ohh ur working with the handles PR? i havent worked with that yet so i dont think i can help
commands.spawn((
Transform::from_xyz(3.0, 2.0, 0.0),
ScriptComponent(vec![asset_server.load::<ScriptAsset>("test_system.lua")])
));```
yerrrp. I'm assuming it might just not be working ATM
though interestingly
local static_script = ScriptAttachment.new_static_script(script_asset)
local system = system_builder("test_system", static_script)
:query(
world.query()
:component(TransformType)
)
world.add_system(update_schedule,system)
does not crash.
though the script attachment isn't loaded in: WARN bevy_mod_scripting_core::bindings::script_system: Dynamic script system test_system could not find script for attachment: StaticScript(script: id AssetId<bevy_mod_scripting_core::asset::ScriptAsset>{ index: 0, generation: 0}). It will not run until it's loaded.
Feels like I shouldn't be creating a new ScriptAttachment if it's managing a scripts' own systems, no?
damn yeah im behind, i need to use 0.15 and migrate to handles
the attachment here is just a reference (it doesn't actually attach anything, that struct in itself has the job of describing a way of attaching a script), if you want to run the system on the current scripts context, you need to use the same attachment, This was previously more muddied, as in we were referring to the script id, which essentially referred to either static script or entity script with this name. There is perhaps a case to be made if we should also have a script_attachment global equivalent, I wasn't sure what'd work best
why do you not have access to world here? It'd be difficult to do safely, but you could do the same thing that with_reflect does under the hood and get the ReflectAllocationId, use the allocator to fetch it, and use the reflection path to walk down your reference
Hmm persisting across saves is something I'd recommend having specific hooks for like on_serialize and on_deserialize and then let the script reproduce the runtime state
It's because I'm trying to implement the MapEntities trait which needs this stub
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M)
Currently what I was thinking is creating a new struct that creates from ScriptValue using world. Or serialize deserialize as you said but I'm not sure how to keep entity references in scripts.
Ahhh, I see. Bit confused on how you might create and run a system then. What is system_builder expecting as its second parameter?
Am I right to be passing script_asset? Or?
@old trail I'm migrating to handles. it seems add_context_initializer() stopped running? The provided function just doesnt get called. Did something change with that?
Passing in script attachments is correct
Is that after attaching the script? I.e. adding the component or static script marker? Scripts now only get evaluated when attached
Not sure what u mean. I have a script that loads and runs fine, but the context initializer doesnt seem to run at all (my prints dont print). Il need to look into it more to get more details cus im weirded out by the fact that there are no lua errors. Im importing packages, but that cant work if the context initializer doesnt work. I should be getting "null pointer exceptions". Anyway, the first thing i noticed was that the intializer wasnt running so i need to fix that first.
Im adding it as a static script
To be more precise, what I have now is the following:
- player presses F5
- on_save lua call script sends ScriptValue I save it in a resources
- bevy_save does a snap shot that includes this resource.
Then
- player presses f9
- bevy_save applies snapshot (this is where ReflectMapEntities is applied to remap to the new entities )
- I send the data back to script with an on_load lua call.
but perhaps using serialisation would work for entity too? I'm would have to look into that.
@old trail Damn, please disregard. It does seem to be working, i just kept missing it somehow.
The search feature in the vscode terminal is a bit wonky.
Hi, I'm trying to integrate bevy_mod_scripting (feature luau, version 0.15.1) into my game and it seems like I'm missing types. When dumping the types global variable I see that i'm missing entries. For exemple i'm missing types.Color and types.LinearRgba. The dump is performed inside a callback of a static script.
I there a specific feature i need to enable for these types to be visible ?
I have bevy = { version = "0.15.3", features = ["dynamic_linking", "file_watcher"] }in my project and it works fine. But perhaps bevy_reflect is not by default for 0.15.3? (works on luau + bevy 0.15.3)
Color has #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]so it does seem to be dependent on a feature?
let me try and dump the type global myself.
I have it :
Color: <Reference to Allocation(11280)(bevy_mod_scripting_core::bindings::query::ScriptTypeRegistration) -> bevy_mod_scripting_core::bindings::query::ScriptTypeRegistration>
i'm on BMS 0.13.0
Yeah I think they’re on BMS 0.15.1
Yeah, but I don't see why that binding would have dissapeared, unless bug?
I'm on bevy 0.16.1 and bevy_reflect is enabled by default
Ah. BMS is not compatible with bevy 0.16
bevy 0.15.3, unless you are on the specific 0.16 branch, but that currently has regression because of a blocking issue on bevy 0.16
This issue.
Okay, thank you
Oh cool, i think im done with the handles migration.
ok so im assuming u already have this resolved, but u need to pass in a scriptattachment like ur doing there and that works for me. If ur still having issues let me know
Yeah I was able to figure that it took the scriptattachment but couldn’t quite figure out how to get any scriptattachment i wanted
i.e., if i wanted to run ‘test_function’ in the same script context, i don’t think there’s a way for me to get the script’s own scriptattachment
Uhh, so idk how ur handling the assets but im using bevy_asset_loader and so for me the binding to get that done is pretty simple:
pub fn get_system_script(
ctx: FunctionCallContext,
script_id: String,
) -> Result<Val<ScriptAttachment>, InteropError> {
let handle = ctx.world()?.with_global_access(|world| {
let my_assets = world.resource::<MyAssets>();
my_assets.get_script_handle(&script_id)
})?;
Ok(Val(ScriptAttachment::StaticScript(handle)))
}
The handles are stored in a HashMap<AssetFilePathName, Handle<ScriptAsset>> in MyAssets and get_script_handle just retrieves it via the "Script id". The details for u in this case might be different, but the general idea is still the same.
🫡 Appreciate it. I figured I needed to grab the handle somehow but couldn't figure it out last night.
Yeah, unless theres a BMS api method im missing, this is pretty much the way to do it.
@old trail
Would it make sense to add an Entity(Entity) inside ReflectBase? This would allow the MapEntity to directly change the Entity if the reflect is just an entity, and if not it could call an error because it's trying to map something that should not be mapped?
/// The Id of the kind of reflection base being pointed to
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
pub enum ReflectBase {
/// A component of an entity
Component(Entity, ComponentId),
/// A resource
Resource(ComponentId),
/// an allocation
Owned(ReflectAllocationId),
}
This would allow to directly implement MapEntity on ScriptValue and therefore making it very easy to map entities as we save/load.
Example code with a component
ScriptValue::Reference(reflect_reference) => {
match &reflect_reference.base.base_id {
ReflectBase::Component(entity, component_id) => {
let mapped = entity_mapper.map_entity(*entity);
let mut new_ref = reflect_reference.clone();
new_ref.base.base_id = ReflectBase::Component(mapped, *component_id);
ScriptValue::Reference(new_ref)
}
// resources and owned allocations don’t need entity remapping
_ => {
error!("Attempted to map reflect_reference, that was not component");
ScriptValue::Unit
}
}
}
I'm going to check how well it works on components this afternoon.
I just confirmed that mapping components with this approach works.
Implementing MapEntity and ReflectMapEntity for ScriptValue would likely be possible by separating ReflectBase with a new Entity(Entity) enum type. This would then make it very easy to keep track of entity references during state changes, saves and loads.
I'll create an issue on the repo to have a trace of my work somewhere.
Wait, BMS 0.15.1 is for bevy 0.16...?
I was trying to upgrade and getting some silliness...
@woeful mirage I got it wrong as the newer BMS 0.15.1 is compatible with bevy 16.0, I just hadn't double checked. It still has the regression from the issue I mentionned, though it is usable for things.
Ok, I this case there must be a problem for types to be missing. Maybe the type registration is done too late ?
Maybe something that changed between bevy 0.15.3 and 0.16.0, or maybe a change between 16.0 and 16.1
Yeah probably, I'll try to debug it tomorrow
That's also my theory
Yeah it seems those impls moved from bevy_reflect to bevy_color and for some reason that crate is not being codegened
I am gonna look into making the crate graph calculation better and more transparent
Conceptually it does not really make sense, reflect bases indicate types from which you reflect further via string paths, Entities are allocations which are covered by the owned variant, adding specific types in here makes things needlessly complicated, even if you did that you could still have Entities referenced via the compnent and resource bases too
It'd make more sense to add a variant to ScriptValue for Entity specifically
that has some performance benefits
That would make sense. Thank you very much for the input on the idea. I'll look into it and might implement something on 0.13 as I'm still using world in my scripts.
when writing that I sorta made the assumption script writers would know if their scripts run on entities or as static scripts etc, but correct me if I am wrong
oh and since now scripts only run when attached, callbacks actually should always have access to entity/script etc
@old trail Random thought: lets say i had a game that allowed people to script logic directly into the game via an in-game editor. And the users simply queued commands from the scripts so they can indirectly make changes to the world. I.e Move(direction) could be one of those commands. For debugging purposes, i may way want to visually show the queue of commands or a history of commands and tie each command to a line of code in a script. Is that something that I could do today with BMS, and if not, could it be theoretically possible later?
I am guessing you're wondering specifically about getting the current line of code in bindings?
Specifically, lets say the user calls this in their lua script queue_command(Move(direction)) on line 5 of the script. So queue_command would be the rust binding and Move is just data. It would be nice if queue_command knows what script and line it was called from to enable my scenario.
And again, just for debug purposes. If a command is showing up unexpectedly for the user i can see that being very helpful in figuring out where the issue is. This is a very minor requirement btw and its not even for the game im making atm, but i was thinking for other future games.
At least in Lua, I think so, through the stack or something like: https://docs.rs/mlua/latest/mlua/struct.Debug.html
Ah cool. Thats good. Im guessing "currentline" under lua_Debug would be it. I have no idea how id use this tho. Is that something BMS would need to help out with?
I think the easy way would be: https://docs.rs/mlua/latest/mlua/struct.Lua.html#method.set_hook, setting a per line hook in mlua, which sets some thread local that you can use in the binding that follows.
Otherwise you'd have to directly interact with the lua stack machine I think
oh wait that's luau
Id consider using that instead of luajit if i had to, nbd.
Top level Lua struct which represents an instance of Lua VM.
ohhhh, that also looks promising
damn I was upgrading the edition to 2024 and this https://doc.rust-lang.org/nightly/edition-guide/rust-2024/never-type-fallback.html tripped me up
I had to add the () or rhai was expecting the never type ! lmao
Oh my....
As I'm starting to dig into things I'm like... well... Shouldn't I also implement the luau vector class?....
There is still this problem of needing to have access both to the ReflectReference for lua and the Entity for MapEntities.
So either I create a ScriptValue::Entity(Entity) which works fine for the converts, but not for lua as it would need a ReflectReference, or a ScriptValue::Entity(Entity, ReflectReference), which becomes a pain for things like From<Entity> or From<ReflectReference> (unless I do all the process that reflect does as you mentionned).
And if I go for ScriptValue::Entity(ReflectReference) I would tend to want to hit in ReflectBase which is back to this.
I'll think about it more.
Maybe a dummy component...
I don't know if trying to serialise everything is the way to go, personally my instinct would be to create a resource which is stable across reloads and use it to recreate state
The way I want this to work is for users to be able to have references to entities inside a script, that way they can interact with the world however they can with full control over inserting components and such.
I could artificially do that by having a component that has a reference to it's own entity but it feels weird.
What would you put in that resource?
That resource would end up containing ScriptValues no ? Then the ScriptValue need to be entity mapped when saved?
I'm going to go with a dummy component for now as a workaround, it's frustrating to see components being easily mapped but not entities...
I have it working now.
The script has a reference to a component that refers to it's own entity. As I save and load the path is mapped allowing to keep references to the entity despite the state change.
This is a script modifying the transform of two entity, the entity are despawned and respawned and the reference stay valid as they have been updated by MapEntities.
The down side is that a deleted entity leads to errors instead of null values.
damn, rhai is actually quite bad for build times:
I'll need to think about serializing/deserializing in general, BMS doesn't currently have much in the way of utils for those things
I am thinking I might be able to pre-expand the macros generated in _functions, to cut down like 80% of the compile time
Would a "SerealizedScriptValue" that is created from world and a ScriptValue could work ?
Such a thing could be entity mapped for sure.
@pale fern I would love to know if https://github.com/makspll/bevy_mod_scripting/pull/462 reduces your compile times much
Are we talking consecutive compiles or clean compiles? BMS only seems to bother me when i need to do a full recompile of it, which only happens when i change something in BMS itself.
clean compiles
Ah, actually it did not nearly affect the compile time as much as i'd hoped :C I guess the script_bindings macro is not at all slow
I'll still try it out. I also found something wrong in my laptop that was slowing it down, so hopefully the compile times will be more reasonable
another thing I can try do is instead of depending on all of bevy, point directly at its subcrates
that might decouple the compilation quite a bit, and let us parallelize functions earlier
Well it did speed up by about 40 minutes in my machine
seriously ?
damn
I am working on splitting out the crates too, so that should give a big boost
oh yeah that was me being positively surprised
😄
I guess the macro work load just happens to be super slow for you
Yeah, my laptop is pretty unpredictable
what was your total build time ?
that's crazy
I was at 28minutes for a clean build last time I tried on osx mbp 2020 intel maxed out.
(not sure if sharing that info is useful)
Honestly I think clean build time is one of the annoying things... 40GB for a debug build is silly.
Ah... Maybe I'm doing something stupid in my toml....
Or maybe the osx target is silly
I would tend to think I'm silly first though.
Do you use the opt-level = 1 and = 3 options?
Yeah I think so. I don't have my toml on hand right now though.
Actually it's there. #1406122398276653107 message
An older version but it's very similar.
I didn't test things too thoroughly so I don't have much data
that was with a AMD Ryzen 9 7950X
If you're willing to chip in with a few hundred dollars, I'll gladly let you help me
Lol. Not surprising.
my clean builds are usually around 12 minutes
Processor: AMD Ryzen 7 6800HS with Radeon Graphics, 3201 Mhz, 8 Core(s), 16 Logical Processor(s)
and then for non clean its about 3-6 seconds depending on the alignment of the stars.
my next pc will hopefully be sub-second 
I have the same 3-6 seconds too.
did...did i waste a bunch of money on this CPU?? lool
Maybe 10-25 when I start doing changes.
10-12 is the highest i have seen but those are rare
Nah I think the link might not be parallel ?
The moment I hit many crates in my project it goes up to 25
Yeah, the linking always seemed like the bottleneck to me
With cranelift and generic sharing it got my iterative compiles from 10 to 15 minutes down to around 1
Cranelift doesnt seem to do anything for me and generic sharing has always been on
Did you separate in crates ?
nope
Try doing that it Maaasssively improved my project
I haven't tried it. Does it speed up the compiles a lot for you?
And also makes it much cleaner to organise.
oki dok
My older pc had 90 second non-clean compiles. It was miserable. I upgraded as soon as i get serious with bevy.
You a game dev?
I used this as inspiration, but most project have separate crates, bevy, BMS.
solo indie first game
Oh woah
My my. Good luck with that ! You have some Dev blogs or something laying around ?
I'm planning on using bevy inside escape games. I want to use my knowledge to create art.
We'll see if it still holds after I do market analysis and my technology evaluation
Nah, just living one day at a time lol. I struggle with productivity so working on the game is the most i can expect of myself.
Later on il start a discord for it and might do something like that there
But thats way later
I've been wanting to create something like Artemis or Empty Epsilon for years but it's been on the back burner
I tried to join empty epsilon but I don't think I can get over the limitation they have in their game. Hence me evaluating bevy.
By artemis do u mean this? https://store.steampowered.com/app/1789680/Artemis/
Artemis bridge SIM
Ah found it
Artemis is designed for anyone who watched Star Trek and dreamed of what it would be like to sit on the bridge of a star ship.
Artemis simulates a spaceship bridge by networking several computers together.** You cannot play Artemis single-player!** One computer runs the simulation and the "main screen", while the others serve as workstations for…
$6.99
296
This feels like something bevy would be good at. But obviously if u care about modding then maybe not the best choice. I love bevy too much to pick something else so here i am lol
Empty epsilon has a fantastic modding lua interface which is why I'm here in the first place.
Honestly the Empty Epsilon people have done wonderful work. And working on their codebase really helped me give an even richer vision than I originally had.
My game is basically Total War x Paradox games
Noice !
The idea is that it be a highly moddable game, which is why I'm so invested in this crate
Modding is such more work to setup though...
Now that I've applied snapshots, I'm going to have to do actual save files and load them.
In a fresh game restart
Yeah, but I like modding, and I'm building the game from the ground up to take data from external files that can be easily edited
Yep. Doing the same also
So for example the map is saved as a csv, and there's an editor mode to be able to create your own
For me everything will be lua based.
I think that's a good idea, but not to define the huge amounts of data I need for the map
Oh really? I'd been using csv::ReaderBuilder
There has to be function to read CSV files no? We were reading JSON files in Empty Epsilon in lua.
But then maybe the sandboxing in BMS will not allow you to do that ?
I know that the sandboxing prevents you from requireing, but there's also a feature for that
Yeah I use require everywhere. I have a gameEntiy library in the context of my work
To abstract all the world and such from scripters
My ship template are lua defined and also a require.
Forget this junk then, I'll just load the data with lua
Though I do like the way I define cities. A toml file for each city recursively loaded
The problem with that is that currently require causes problem with hot reloads.
If file A requires B and B is reloaded, A is not reloaded too.
Oof
Yeah a bit of a pain but easy to avoid.
I'm debating with myself wether to move my ship templates to toml.
We'll see it's part of the things I need to figure out as currently, templates are not propagated.
Because of this.
Also, this doesn't matter most of the time because the main library doesn't change much.
i just got my build time down by like half by pointing at bevy subcrates:
I think mostly by removing unnecessary crates, and slightly imroving parallelisation, not sure how it will affect end-users who will pull in most bevy crates
actually really good:
2 minutes!
:0
Oh wow that is really impressive.
Do you think it's worth it to do it on game level ?
Also what tool is this ?
this is cargo build --timings
you mean in user crates ?
I did not know that existed. Cargo has everything.
I mean while using bevy/ BMS for a game
hmm, I think probably yes, or at least thinking about disabling features from bevy you don't need
Because I don't think bevy and BMS will be compiled often
I'll have to see about it later.
I'm excited to order my save calls properly and pause the game to make sure all the save data is synched and have an actual save file next week.
Which is probably the highest risk in the project currently. Then the final step is networking.
I think with the recent changes coming in handles, BMS will be slightly more deterministic, in that you control exactly when scripts will execute (when you attach them), but we'll still need to make all of the queries use deterministic versions to get fully there, so you might bump into that with networking
Handles is already here, u saying its getting more changes? Will this require a migration on our end?
Oh nah, I mean the handles changes
The scripts won't be sent over networking the way I plan it.
The script is an API for the server instance to interact with the game model. The game model will be what is network replicated.
Basically I want people to be able to create scenarios and stories using my foundations.
However having instant script execution could be nice to avoid a pause and a back and forth between events for saving.
Can you share your camera zoom mechanics? chefs kiss
@vast dock Out of curiosity, how did you make the 2D grid in your spaceship game? Trying to do this on another project if you could share.
Code has not changed from this. But it's not going to be the final thing. It's a placeholder.
I'll likely use a shader in the final thing
Thanks!
I'm using Bevy RTS Camera and controled with Bevy Enhanced Input
Will have to split out each individual binding feature into its own crate I think
but that's for later
@woeful mirage this wll bring back generation for bevy_color
if I was to split out the bindings crates into their own individual crates, what should the naming scheme be? bevy_asset_bindings bevy_asset_bms_bindings ?
yeah, mostly worried about not stealing a crate bevy itself might want later
Oh i gotcha, in that case might as well go with the other option then. To be safe.
true
publishing is gonna be interesting:
@old trail am i going crazy? Can i not set an Option<t> field to nil when constructing in lua?
Well, looks like that was actually my first time trying it. Interesting.
Deriving default doesnt seem to do anything
Just checked on my end and I have a problem with nil too on 0.13
#[derive(Component, Reflect, Default)]
#[reflect(Component, MapEntities)]
#[require(CollidingEntities)]
pub struct SimpleMissileAI {
pub target: Option<Entity>,
#[reflect(ignore)]
pub previous_target_pos: Option<Vec2>,
}
function ODEntityT.set_simple_missile_ai(self: ODEntity, target: ODEntity?)
if target then
world.insert_component(self:get_entity_ref(), types.SimpleMissileAI, construct(types.SimpleMissileAI, {target = target:get_entity_ref()}))
else
world.insert_component(self:get_entity_ref(), types.SimpleMissileAI, construct(types.SimpleMissileAI, {target = nil}))
end
end
Giving it an entity ref works, but giving it a nil fails.
ERROR bevy_mod_scripting_core::handler: error in script `lua/scenarios/missile_intercept.luau`: Error in function construct : Missing data in constructor for type: core::option::Option<bevy_ecs::entity::Entity>. Missing data: target.
Yeah i get the same error. For now i just made a binding that acts as a constructor method (which is for the the best)
I am thinking, if the field is nil, maybe lua does not create it?
Thats prob whats happening. Tbh i feel like i already had this conversation before but i forgot what was said about this.
But also nothing stops you from assigning nil just after it was constructed and create a lua wrapper? Or that would hit the world flush limitations from bevy 0.16?
You are on bevy 16 right?
The problem is that i dont even know how to initialize it in the first place without going through rust. Existing values that have Option can accept nil. Function parameters that are Options can as well. Its specifically a construct() issue. I am on 0.16 and using bms 0.15.1
I think this is a case of ReflectDefault missing on your field type
Bevy Scripting Plugin. Contribute to makspll/bevy_mod_scripting development by creating an account on GitHub.
Or not being registered in the type registry
I do remember we had a conversation around this and agree we should also look for a default at the constructed type level
yeah, to figure out what dependencies I need in the generated crates, I need to understand the crate graph within the bevy workspace, and AFAIK there's no out of the box way to do this to this level of detail
What is the name of that tool?
I'm really enjoying hanging out here, I'm learning lots it's nice.
crate_feature_graph but it's unpublished yet
I just managed to get the saving all properly ordered using states. It's pretty nice and it makes sure all my data is properly handled, final step is to write it down to a file.
Then I think I might try to look into the blocking issue at bevy as I want to catch up to the latest version of bevy_save and avian before starting to do networking...
Yeaaah thanks for reminding me lol. I am forgetful. I think i only put ReflectDefault on the outer struct. The type in question was also a struct.
The splitting of crates btw, with all of the additional code, I am at 7minutes clean compile time (like 10x more crates being generated), so pretty good:
Oh wow that is a massive improvement from earlier.
cargo build --timings
all of these build time improvements are live on main now
Yeah you've done so much, that's amazing!
I'm digging more towards the saving things. I'm now getting into the serialization/deserialization as I have managed to save my game to a JSON file.
I'm going to see if I can implement the needed traits like I did for map entities
is there a simple way to detect what ScriptValue::Reference is pointing to without world?
I got it by saving world.component_id::<Component>();
It's a pain to try to recreate ScriptValues without the allocators and references xD
I once looked into making the allocations actually use a global allocator, but the allocator api is unstable and bevy doesn't hold alignment information for types via reflection yet
Honestly I'm not too far, I managed to go all the way down to entities from a ScriptValue into a json save file.
Now I'm trying to get back out of it.
But I'm not sure I can do that without changing BMS itself because of private fields and such...
Looking at some of my code that uses the allocator.... it seems pretty trivial. I feel like that could be automated whenever u pass in values to a ScriptCallback. I.e it feels very boilerplatey to me. Am i missing something?
There's some nuance about protecting the lifetimes: https://github.com/makspll/bevy_mod_scripting/commit/47069140cc624142f8cffc86ed4af265484f46ae
Ah, in that case il never understand 😅 I have a weird relationship with lifetimes. I get it but I also dont get it.
Ha ! Now I have loading for everything except the entity reference...
same tbh, here you just need to make sure the allocations don't get dropped while a reference is active, i.e. trivially when no scripts are running you're good
Any idea on how to force create a component ScriptValue::Reference(), with the ComponentID, or TypeID, or anything outside of the BMS crate without the existing allocators?
Technically for a component all the data is still valid after load, but I need to feed it to private values...
Right, but i was just talking about initializing the scriptvalue, i.e
let mut allocator = allocator.write();
let entity_payload = ReflectReference::new_allocated(trigger.target(), &mut allocator);
let entity_val = ScriptValue::Reference(entity_payload);
I feel like im doing this with everything, regardless of the type (of course as long as I need it to be a reference and not just a primitive)
So this part i felt could be automated,
oh right, yeah that's true, I think the only thing is if you hide the allocator .write() bit you can cause deadlocks
So the fact that line is happening in that moment (the scope, really) is important? Cant just move it out?
Wait do you mean having something like ScriptValue::new_allocated(payload, &mut allocator) that would be perfectly fine
Well, that would be an improvement for sure but it still requires we get the allocator, which i suppose is the point for the lifetime protection? Cant get around that i guess.
And hence the global allocator u mentioned before.
ScriptValue::new_allocated is a good idea tho, def throw that in the to-do list. If u can update examples to use that, it can make that whole process seem less intimidating.
But ideally i could pass in a ScriptValue::UnprocessedReference(MyValue) and somewhere down the line the 3 lines i posted (including the query that gets the allocator) gets run on it and turns it into the ScriptValueReference. But...yeah not sure if thats feasible.
@old trail RE: https://github.com/makspll/bevy_mod_scripting/issues/12, I'm an un-invested but curious third-party: would there be resistance against scoping down this issue to a simple implementation of Serialize and Deserialize for ScriptValue ? What are some of the technical challenges here?
I've added a big documentation on my workaround on that issue.
I now have managed to make the save and load work, I am currently saving to a json for human readability, but I'll do it on the binary format bevy_save use later.
Is there an issue in BMS to fix the regression due to https://github.com/bevyengine/bevy/issues/20395 ?
I didn't see any and had to scroll up discord. If not I'll create one.
Also we should update the compatibility table in the README.md, iirc BMS 0.14 bumps to bevy 0.16.0?
Lots of folks here use Luau for type annotations right? Might migrate to that.
Also, anyone think a pypy implementation would be interesting? I bet Lua almost certainly still outperforms. But would be interesting to benchmark a Python JIT.
I'm unsure of the implication of luau versus lua as I lack the experience. But after having setup the dev environement properly for auto complete it's quite nice to play with!
I think luajit is faster tho?
Luau has comparative speeds to LuaJIT
Not better performance but still competitive
I know there’s also LuauJIT but IDK what that’s like
The type checking makes it well worth to use over JIT imo
And Luau has support for continue and x += 10 🙂
Later when i start doing benchmarks il see how much it affects me
I'm curious at how easy it is to integrate considering it's in mlua
We should also add the support for mlua vector class in BMS? We might get some slight performance increase ?
Nvm it's a roblox thing it seems...
sorry im not sure what that is..
Well, we have Vec2 and Vec3 and those have bindings for us to use in the scripts
Uhh, i use Vec2.new() actually
Uh....
but i cant remember if i made that binding for myself or if its provided by bms
seem to be from this?
Not for everything but some have the bindings for it, let me check something
Or at least they have Vector3.new()
Documentation for the Bevy Scripting library
Maybe I don't have that on BMS 0.13
new is listed there, so yeah its in BMS
Ah.
and not just that, a bunch of methods are there too
Would make sense.
seems gedes gave it some love considering how important it is
There are some types that dont have a new binding
I've launched a fresh build trying to upgrade to bevy 0.16 as i think the blocking issue won't affect me in the end... I've been waiting for 15 minutes so far...
makes sense.
Eh.. I should pay more attention to the doc I do unecessary constructs...
Sometimes il make a binding just to avoid using construct haha. Low key dislike that method. (no offense gedes <3)
If we ever get into trouble with luau performances we'll integrate luauJIT from mlua.
Yeah mine are all hidden behind luau bindings.
Yeah that probably wont be too hard to use
Most likely.
I'm currently upgrading to bevy 0.16.1, Is there a simple way of having the previous behaviour when loading a script? I'm trying to execute the the script once and then deactivating it (as I need it to register some data to my app)
I tried doing something like this, but they don't get removed:
commands.queue(AddStaticScript::new(handle.clone()));
commands.queue(RemoveStaticScript::new(handle.clone()));
RemoveStaticScript doesnt remove the script?
It probably does, but I'm calling it within the same function so the commands are one after the other
So I'm guessing maybe some orders somewhere is calling remove before add ?
I didn't have time to debug much yet.
oh i see, yeah maybe ur removing it too fast before anything can happen. Delay the removal by 1 frame.
It does add, but does not remove. All my scenarios are started at the same time and it's chaos ahahaha
U should set up a minimal reproduction and share it with gedes if u think this is a bug.
It's not a bug really, it's about choosing if you delete or create first. I think?
Something in the line of this.
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let handle = asset_server.load("path");
commands.queue(AddStaticScript::new(handle.clone()));
commands.queue(RemoveStaticScript::new(handle.clone()));
}
I had to delay the delete by a few frames but it works.
Other things are broken in my code though, I think it's also to do with a change in operation orders.
One thing that might be a bug though, this
commands.queue(AddStaticScript::new(handle));
events.write(ScriptCallbackEvent::new_for_all_scripts(
OnStartScenario,
vec![ScriptValue::Map(map)],
));
Did not seem to shoot the the OnStartScenario callback in lua when it used to before.
I'll look into it when I have time later on.
These are some interesting "edge cases" that @old trail might be interested in knowing about at the very least.
I will need to triple check if I didn't do anything stupid while migrating to bevy 0.16.1
Min repro for both issues : https://github.com/Ownezx/bms_min_repro
I created two issues.
Also RemoveStaticScriptis not in prelude
But that might be intentional?
Might be calculated.
that is definitely viable I think although I'd rather make the allocator itself static than add more variants to ScriptValue, as those make all impls more complicated
There wouldn't be resistance to it, there are many considerations though as to and simple serialization might not be enough to persist state across runs:
TypeIdI don't know if you'll be able to easilly deser thisReflectAllocationIdis essentially a pointer to aPartialReflectvalue living inside theReflectAllocatorwould this require additional bounds here? If you don't persist the actual values the references will be missing their values- unserializable variants, like
ErrororDynamicScriptFunction
it is provided, all the glam math classes have special bindings
none taken lol, it's the lowest level constructor you'll ever have to use, it's meant to be slightly annoying
are your scripts loaded when you call these commands ?
if not remove will likely run first, because Add waits for loads to complete
I would reccomend manually using the create script / run callback /delete script commands on short lived scripts like these
similarly here, if the callback event fires before the script gets attached it won't have a script to run on
Ah. Maybe I'm silly and didn't attach a handle reference anywhere.
What I was doing used to work, but yeah I'll look into the manual load commands.
@old trail Auto-registering of reflect values is coming soon, which to me seems like fuckin magic. Do you think its possible for BMS to have auto registration of bindings? Not a big deal but its one of those annoying boilerplatey things.
if you mean calling register_X_functions on your app for each binding block
that could be automated
yeah exactly, damn thats dope.
yeah scripts execute slightly later now, i.e. when they're attached not immediately, but I have a feeling if you ensure your script asset is loaded before running that command it should work
I managed to get everything working by putting frame delays to the execution
On a first load I put 30 frame although I might get away with less
Once they are loaded, when reloading I can just put one frame delay on the callback.
I've managed to completely update to BMS 0.15.1 and bevy 0.16.1
Thoughts on https://github.com/makspll/bevy_mod_scripting/pull/470 ?
Summary
Removes ContextLoadingSettings and instead moves its innards into a ScriptingPluginConfiguration<P> struct,
which can be accessed via P::readonly_config(world.id())
This shoul...
getting closer to fixing the handler resource removal problem
I've yet to use the handler context so I can't really have critical feedback. But the description in the PR seems to be resonable to me.
I tried out cargo build --timings=html, on a clean compile, and thanks to splitting the code into different crates and the improvements on the bms side, it took the compile time below 1 hour
52m40s to be precise
I have plans to split out bms core even more so hopefully one day that'll be under 40
Also feel free to close my issues if they are not relevant @old trail
aand I can get the context to just this:
now it's just a case of Arc ing the context resource and voila you no longer need to remove any resources to handle callbacks
Oh wow, that is sweet! I remember having to remove resources at times!
Wonderful job!
Thats very sick. But, is there a performance cost to this? Does it only hit once per callback invocation?
The slowness of lua might dwarf whatever Arc introduces so it might not matter.
oh yeah almost certainly. interpreting a language is waaaay slower than dereferencing a couple pointers lol
ofc, it depends exactly what's going on in there. would looooove to see some benches :D
Yeah it will be slightly less cache friendly, surprisingly it might be faster since we are avoiding resource removal and reinsertion
Unsure if you'll find what you want in there but there are some benchmarks.
All in all minuscule impact most likely
thank you! it's great that we have these :)
also... that's surprisingly fast..? thought that stuff would be waaaaay slower tbh
Napkin math is like 9k 4 argument lua calls to rust bindings (empty) per frame at 60 fps
Awesome change then. Ship it!
this will alleviate the problems introduced in bevy 0.16 w.r.t callbacks, commands running simultaneously will be able to use the script contexts, as long as they are not trying to modify the same one
Awesome.
ty for all the good work ❤️
Omg that is actually amazing !
first crate to be pulled out will be bevy_mod_scripting_asset https://github.com/makspll/bevy_mod_scripting/pull/475
then it might be bevy_mod_scripting_dynamic_ecs or bevy_mod_scripting_error, generally heading towards reducing cognitive load in each area, as well as making the boundaries much clearer
Im starting to really hate lua
How so ?
i cant enumerate over a rust vec.
Its just one of many things thats missing from rust.
what about?:
for v in pairs(res.vec_usize) do
iterated_vals[#iterated_vals + 1] = v
end
@old trail actually for that specific thing, how hard would it be? im talking about .iter().enumerate() . It works nicely with lua cus its basically ipairs
Uhh, not sure what this is tbh lol. for val in vec:iter() works already. But with enumerate it would include the index as well which would fill a common need, i would think.
yeah so in rust u would have something like for (i, comp) in components.iter().enumerate()
which looks a lot like ipairs anyway
iter by itself only returns a value in lua, if im not mistaken.
im finding myself needing to create index variables outside of the loop and manually incrementing them. Which is gross lol.
Its a bit annoying since you can't define bindings on the function that iter returns
But something like iter_enumerated() should work AFAIK
Awesome, have it done by tomorrow morning. Along with those TPS reports.
I'm currently figuring out unit tests. I'm staying within cargo test for now. I'm going to have to do the lua integration test soon
I've gotten somewhere with splitting out bindings and display things into different crates:
Cooking making these easier to use via statics/thread locals
You can have a look at: https://github.com/makspll/bevy_mod_scripting/blob/3004915ee71d73def80d91c9712de4b8560b1bd4/crates/testing_crates/script_integration_test_harness/src/scenario.rs, driving integration tests from "scripts" or "data files" is very fun
I'll look at that when I finished unit test and start integration tests.
But it looks super dope
if im too incompetent to help contribute code, then i shall contribute memes
all contributions welcome 😄
I am now working on tightening the script loading pipeline
turning this whole thing into a compile time typed state machine:
ideally people will be able to insert systems to manipulate the process at various points
Oh that sounds noice !
You are off doing fancy things!
interesting 👀
it's going to be ultra parallelizable too:
Oh wow. That is sweet !
but with script loading, do we really need that? I suppose performance improvements for the initializers that execute before each script callback could make a difference but im not sure how a bunch of them would be running at the same time?
not really, although you would notice a difference I imagine if you're loading thousands of scripts, the main benefit will be how modular this will become
also this will mean that if you load a batch of scripts, all of their on_script_loaded events will happen in the same time step, as opposed to each script loading wholly before the next one does
Will we still be able to control the load order of scripts?
Yeah, the pipeline will run in the order you attach scripts, only difference is if you run them all in the same frame, they will load semi parallel as it stands. Currently if you do the same (attach a bunch of script component) i think rhey will run one by one in a random order
hmm, its almost like ur setting it up so that if we had a load order and wanted to load them in a specific order, we would need to do it in a system since they run once per frame anyway.
Would just need to set up a queue i guess
That would save me from waiting frames for a load/delete sequence
Can BMS run on MinimalPlugin? (I'm trying to do unit tests)
I get some errors:
ERROR bevy_asset::server: Could not find an asset loader matching: Loader Name: None; Asset Type: None; Extension: None; Path: Some("lua/scenarios/missile_test.luau");
Min repro :
#[test]
fn min_repro() {
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.add_plugins(DiagnosticsPlugin);
app.add_plugins(AssetPlugin::default());
app.add_plugins(LogPlugin::default());
app.add_plugins(BMSPlugin);
app.update();
let handle: Handle<ScriptAsset> = app
.world_mut()
.resource_mut::<AssetServer>()
.load("lua/mainSettings.luau");
app.update();
app.update();
let is_loaded = app
.world_mut()
.resource_mut::<AssetServer>()
.is_loaded(&handle);
assert!(is_loaded);
}
If youre running the app manually you need to call finalize
Ah makes sense.
I managed to make everything work when it comes to loading all my script files. Now to unit test the save/load mechanism.
@old trail So because of how everything works, mutating component data that are script values is very awkward:
local my_component = world.get_component(my_component_e, types.MyComponent)
-- data is of type ScriptValue
local data = my_component.data
data.amount = 32
my_component.data = data
If i try to mutate data directly, i.e my_component.data.amount = 32, nothing happens. The problem is that ScriptValue becomes a plain old lua table. Not a reference to a lua table inside the component. I feel like i may have brought this up a while ago but i forgot what was said 😅. I am curious if i can do anything so that it acts more like a reference in this case?
So the thing about ScriptValue is that it's like this by design, and the same behaviour makes it possible to return a variety of lua values etc at all, so making it a reference type would mean that instead of returning and accessing "my_string" as a literal you'd have to "out.0" check variants and all that jazz
When you use it on a component the users have to replace the whole thing
Its a bit of a magical primitive
Whats the intention behind using ScriptValue here?
Well, not sure how to explain it. Im keeping things dynamic so that modders can easily make their own stuff. Certain things are expected to have a state, but i dont want the structure of that to be set in stone. So i just set it to a ScriptValue which is basically an "any" type.
Anyway, i think i get what ur saying but in my case im strictly talking about the Map variant (although a luaType[1] should also be a reference for sake of completeness). Is there any chance that could be tweaked to get the desired behavior?
Today I pulled all ur updates into the local branch im using and it works great! I was able to get rid of my work-around to solve that issue i was having with triggering callbacks from within scripts. Glad we're done with that chapter lol
I think you could experiment with custom types and bindings, but generally maps are annoying to reflect into (as in you can't) https://github.com/bevyengine/bevy/issues/5764
which means any sort of by-reference semantic for dictionaries is going to be difficult
I think your best bet is a custom type with bindings like set_key get_key , by default non primitive types are converted into references
that's good to hear!
I might have to do this for now, but this does lead to a footgun for any unsuspecting modder. Just so im clear, by custom type u mean a Rust type that has bindings on it?
yeah just a rust wrapper around a type that has the semantics you want, probably some sort of recursive map with ScriptValues as tail types?
I'd say it's not a footgun anymore than dictionaries atm
Why not wrap around everything in a lua library ?
It's a bit more work but you'll get all the auto complete with luau
is this in regards to what I was talking about?
I guess the issue is then you still have pass by value semantics, so you have to replace the whole object
Im not very familiar with lua but i have seen some crazy stuff u can do with lua objects. Im being vague cus im not sure how to describe it, but If u can do that with assignment, then perhaps u can make it do that replacement under the hood. Not something I could figure out right now but i might look into it later. The value in question is being passed into a callback that the modder would define, so i could make such a manipulation (if possible) before passing it to the callback.
I've wrapped luau objects with functions attached to them to and from rust.
Although the functions are reatached to the data when luau recieves the struct from rust
So heres an example of what i meant:
function createWatchedObject()
local raw_data = {}
local meta = {
__index = function(_, key)
return raw_data[key]
end,
__newindex = function(_, key, value)
raw_data[key] = value
print("Update:", key, value)
end
}
return setmetatable({}, meta)
end
local obj = createWatchedObject()
obj.name = "Alice" --> Prints: Update: name Alice
obj.age = 30 --> Prints: Update: age 30
obj.name = "Andrew" --> Prints: Update: name Andrew
The solution wouldnt look quite like this but it highlights what I mean
@old trail Ran into some weird compiler errors and i had to make a bunch of changes to /bevy_mod_scripting/crates/bindings/bevy_reflect_bms_bindings/src/lib.rs
Had to do a search and replace of ::bevy_reflect::erased_serde::__private::serde::__private::. Replaced it with nothing so it would go away. This file is auto generated right? Does using ::bevy_reflect::erased_serde::__private::serde::__private::Clone/Option make sense? Cus thats what it was doing and why it was complaining
I’m working on unit tests, and I was wondering why the Rust callback function use FunctionCallContext and not just a worldguard
this is on main right? yeah I had this recently too, my current PR will fix this, I think there's something non-deterministic w.r.t to the path-finding (lol), probably due to the fact there are two identical trait names
when I re-codegened it fixed itself
it's a sort of catchall, adding more "types" of special things that can be injected into functions is a bit intense on the compiler so it's easier to use one thing that has a bunch of utils. Ideally I see us moving towards having some special trait, like FromWorldGuard that works slightly more generically
bencher working as intended 🦾
pretty much on main, yeah. Ok good thing its not just me
so what ur saying is that rhai is faster than lua in every possible scenario. 📝
Amazing work, @gedes! Thank you kindly for the shout out.
Yup! Just in time haha
My they are really close to release indeed...
hey hey! quick question on the upgrade.
right now I have something like this for triggering a callback for all scripts attached to an entity:
writer.write(ScriptCallbackEvent::new(
callbacks::OnUpdate,
vec![],
Recipients::Entity(entity),
));
Am I correct in thinking that I now should be looping through each script for the recipient? e.g.
writer.write_batch(
query
.iter()
.map(|(entity, scripts)| {
scripts.iter().map(move |script| {
ScriptCallbackEvent::new(
callbacks::OnRoundStart,
vec![],
Recipients::ScriptEntity(script.id().clone(), entity),
None,
)
})
})
);
yup exactly! Recipeints / Contexts are a bit more explicit now
thanks!
also, is there some info on how to update namespace registrations? e.g.
register(
"create_peg",
|ctx: FunctionCallContext, pos: Ref<Vec2>, bonus: bool| {
let world_guard = ctx.world().unwrap();
let world = world_guard.as_unsafe_world_cell().unwrap();
let world = unsafe { world.world_mut() };
let mut cmd = world.commands();
let mut entity = cmd.spawn((
StateScoped(GameState::Main),
Object {
pos: *pos,
data: ObjectData::Peg(PegData::Single),
},
));
if bonus {
entity.insert(Bonus);
}
},
);
errors with
This function does not fulfil the requirements to be a script callable function. All arguments must implement the ScriptArgument trait and all return values must implement the ScriptReturn trait
If you’re trying to return a non-primitive type, you might need to use Val<T> Ref<T> or Mut<T> wrapper
And not totally sure why. (Sorry if this is obvious and im just missing something!)
Oh do you have default bevy features disabled? Does tour Vec2 implement reflect?
oh lol yeah that was it i think, thanks again!
hmm okay, another weird thing...
error[E0004]: non-exhaustive patterns: `ReflectRef::Function(_)` not covered
--> /Users/doomy/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_mod_scripting_display-0.16.0/src/printer/mod.rs:38:15
|
38 | match value.reflect_ref() {
| ^^^^^^^^^^^^^^^^^^^ pattern `ReflectRef::Function(_)` not covered
This is in the 0.16 version with bevy 0.16.1. not sure what could be causing - I tried enabling all features just in case it was that.
Do you use function reflection? Bevys specifically
That should be feature gated in bms but if you disable that feature it should dissapear
That enum is a bit problematic
oh, got it. i think i was using the "reflect_functions" feature as I thought it would enable bevy's reflection, whoops. i didnt solve my error i just broke it for a second. enabling default features on bevy puts me back to the "This function does not fulfill the requirements" error
hmm but deleting the vec2 seems to make the error go away, so i guess that is still the issue? it worked on 0.15 so not sure what I changed. also seems to be an issue for Ref<Entity>
oh hold on, i think it might be using the wrong Ref :|
ahhhhhhhhhhh yeah that was it 😭
alright, i think i got all the compiler errors handeled updating! was also wondering if script loading changed from being automatic? It looks like my script assets are loading, but scripts don't seem to be firing off (even if i put some global print in a lua file it doesn't do anything, when it used to on load).
hmm, seems like adding a scriptcomponent should initialize it, reading through the docs, so i might be doing something wrong elsewhere
Yeah that doesnt happen anymore. The script needs to be attached to something, like when u use AddStaticScript
Oh wait, u say u are doing that. How are u adding it?
I'm adding via a ScriptComponent and then triggering. e.g.:
-- writer.write(ScriptCallbackEvent::new(
-- callbacks::OnLaunch,
-- vec![ScriptValue::Reference(ReflectReference::new_allocated(
-- ball_entity,
-- &mut alloc,
-- ))],
-- Recipients::Entity(*backing_entity),
-- ));
++ if let Ok(scripts) = scripts.get(*backing_entity) {
++ writer.write_batch(scripts.iter().map(move |script| {
++ ScriptCallbackEvent::new(
++ callbacks::OnLaunch,
++ vec![ScriptValue::Reference(ReflectReference::new_allocated(
++ ball_entity,
++ &mut alloc,
++ ))],
++ Recipients::ScriptEntity(script.id(), *backing_entity),
++ None,
++ )
++ }));
++ }
Ok so are u saying this isnt triggering or is the global print also not working? I can see this trigger might not work cus ur running it too fast (just need to delay a frame) but the global print not working is kinda weird.
yeah it just seems like everything is sent to a black hole on the lua end
Hmm, now im wondering if maybe the attaching is happening too soon. Like ur doing it while the script is still loading?
Just as a debug step, can u add it with AddStaticScript and see if the global print statement goes off?
yeah, that should narrow down. i am using loading states so i think it should be ok? but unsure how things changed in totality
same here, as soon as everything is loaded i spawn my scripts as static scripts and that just works for me.
not sure what im missing from this that would explain it: https://makspll.github.io/bevy_mod_scripting/Summary/managing-scripts.html#evaluating
Documentation for the Bevy Scripting library
in the provided example, he adds the scriptcomponent immediately
Yeah i think it's a case of waiting, the loading can now happen over multiple frames, you can check if a script is loaded by calling ScriptContext<P>::contains
So is this example wrong?
fn load_script(asset_server: Res<AssetServer>, mut commands: Commands) {
let handle = asset_server.load::<ScriptAsset>("my_script.lua");
commands.spawn(ScriptComponent(vec![handle]));
}
That's still correct, its just it might take longer to be given a context and start responding
Using AttachScript commands will load instantly, but if you then attach the component it will reload later
Well, they were saying they were getting nothing at all. Print statements werent even firing.
If the print statements are in a callback, and the script is not loaded when the callback is fired, then it will be dropped
They said:
even if i put some global print in a lua file it doesn't do anything, when it used to on load
Thats the weird part for me.
Its been a hot min tho so maybe they got it fixed
@old trail I wanna make a binding that takes in a single unique marker component type as an input and then returns the entity that has that marker. Assuming there isnt already something like this (besides using a query inside lua which i dont want to do), is this doable?
gonna try to learn how to do that with component_id before u respond 🏃♂️➡️
Quick and dirty but it works
fn get_entity(
ctx: FunctionCallContext,
comp: Val<ScriptComponentRegistration>,
) -> Result<Val<Entity>, InteropError> {
let entity = ctx.world()?.with_global_access(|mut world| {
let mut query = QueryBuilder::<FilteredEntityMut>::new(&mut world)
.with_id(comp.0.component_id)
.build();
query.single(world).expect("err").entity()
})?;
Ok(Val(entity))
}
@old trail I figured it out...
~~oh no, i think i found a bug? I have a script that is static, and im pretty sure i havent added it again as another static script or as a scriptcomponent, but an event on it is getting triggered twice. When i move the event to a different script that is also static, it does not happen. So its something particular about this script. Based on my debugging, im not triggering the event more than once. Its as if there are multiple instances of the script and they're both just getting caught by ScriptCallbackEvent::new_for_all_scripts
The script thats reproducing it isnt random. Im doing something else with it (kinda complicated) while the bug occurs. Instead of trying to explain what im doing, im just wondering if u know of another way to call an event on a script (with the ScriptCallbackEvent i mentioned earlier) besides having an instance of a static script or scriptcomponent? I suspect this third (and undocumented?) way is whats causing this.
Im not setting a ContextPolicy so i guess its set to "per_entity_and_script" but again if im only spawning it as a static script then this shouldnt matter.~~
oh boy..I was doing something else unrelated to this and ran into the same problem, but this time it couldnt have been BMS's fault. And this time it made it clear. Because i was importing the file in question in another script, the global callback function was being pulled into said script and thus creating a duplicate callback. I ALREADY RAN INTO THIS AWHILE GO AND JUST FORGOT. God fucking damn it, this is such a bad foot gun.
It would be really nice if we could make the callbacks local and figure out a way for BMS to get to it.
I think the best way forward is to keep the current behavior, but also add a global function called "register_callback" that lets me pass in any function and registers it as a callback.
i.e:
local function my_callback(params)
end
register_callback(my_callback)
👀
@rare badge where were those prints exactly, was it directly in the script body, or in on_script_loaded etc ?
Yeah that's a very clean way! yeah generally querying will be what you want to do, and whether it's done in lua or rust is up to you
Hmm, yeah I like this, it would mean your callbacks need to be registered say in on_script_loaded but gives us more control over them. We'd just need a multi-lang concept of "storing" callbacks
directly in the lua script body. i should give it another try sometime this week
Is that because thats how u would detect which script is calling it?
Not sure if it can be this simple, but wherever the scripts are getting stored, u just create a wrapper that includes it and a Vec<Callback> or something.
@old trail decided to try and pull the latest updates in main. Things broke, as I expected. When i queue a RunScriptCallback i get a "No context found for script". When i log the contexts, i can see some but not all. It feels like im sending the callback too soon, but this is happening multiple frames after i queue "AttachScript" so im not sure why that would be the case.
Did some dumb stuff to confirm. It is indeed a timing issue. But i need like 2 or 3 frames of delay. Too tired to say how many for sure. This is not ideal, may be related to doomy's issue. Whats strange is that it has nothing to do with the asset loading. Im waiting for the assets to completely load before moving forward. The unusual delay is required after queuing AttachScript.
For the record, im fine with requiring a single frame delay if we can't help it, it lines up with how state transitions work (buffered). But anything more than that is awkward.
That is working as intended, note you can tweak this behavior, the frame time budget can be configured, see: https://makspll.github.io/bevy_mod_scripting/ScriptPipeline/pipeline.html
Documentation for the Bevy Scripting library
Hi! Niot sure if this is the best place to ask this question but it's the channel where Rhai is mentioned the most 😅
I'm writing a utility to process graphs (using petgraph) with the intention of using those graphs to generate levels, missions, overworld maps, factions, etc. for a game and I have this little utility that reads a grammar file and shows the nodes in Bevy.
To develop more graph creation algoritms I though to add Rhai (but not using bevy_mod_scripting, since I want to keep the graph & grammar part of the code as generic as possible).
The interface between Rhai and Rust works: I added my variables and functions to the scope, the script is parsed and run successfully... however I'm at a loss with Rhai: inside a function VERY simple code gives me ExprTooDeep errors... while outside functions it "parses" and runs and produces nodes and edges...
Can you tell me if this is a known limitation of Rhai's parser, if there's a place that describe how I should approach writing code to avoid triggering it, or if in your opinion I did something wrong? 😅
It's so annoying that I'm seriously thinking about using pyo3 to embed Python...
Oh this is interesting...ty
OK so yeah, that worked. Im starting to understand the work u were doing there lol.
I had to set the time budget to 69 hours
This is something that annoys me about rhai but you can customise the max depth using a runtime intializer
I.e. the rhai engine has settings
@old trail How disruptive was 0.17 for bms? Just curious.
Also I forgot to put an issue but the compatibility table is out of date on the GitHub
Thanks! I started reading the book "cover to cover" and I got to the "64 in global and 32 in functions but hey counting the depth layers isn't easy" part yesterday my late night 😅 and yeah, it's not easy because apparently LOOPS COUNT FOR THE DEPTH, since I had a 3 deep loop over an array.
I find it quite strange that they explain the optimization strategies and the recursion stack etc. before telling me how I should actually write code in the language, but I guess it's in another part of the book or in a different book.
My worry is that the time saved on compiling the algo in Rust will be wasted in trying to debug strange AST building issues... now that I have this stable enough to run and compare I think I'll try to integrate pyo3 (the big advantage being that it should be the "regular" Python I know well)
b crate
yup! Ultimately I think I want to forego rhai and replace it with rune, as a core lang or at least something bit better
Hmm, I'm trying to update Nano-9 to Bevy 0.16, and I'm getting stuck on this code:
#[derive(Debug, Clone, Copy, Reflect)]
#[cfg_attr(feature = "scripting", derive(GetTypeDependencies))]
pub enum PColor {
Palette(usize),
Color(Srgba),
}
which produces this error:
1 error[E0412]: cannot find type `TypeRegistry` in this scope ▐
--> src/color/n9color.rs:15:10
|
15 | pub enum N9Color {
| ^^^^^^^ help: a struct with a similar name exists: `AppTypeRegistry`
|
::: /Users/shane/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0
.16.1/src/reflect/mod.rs:31:1
|
31 | pub struct AppTypeRegistry(pub TypeRegistryArc);
| -------------------------- similarly named struct `AppTypeRegistry` defined h
ere
Any ideas?
Found a solution. I just needed to add use bevy::reflect::TypeRegistry;.
"cannot find type TypeRegistry in this scope" can be solved by adding use bevy::reflect::TypeRegistry;.
(not making fun of u, haha)
@old trail So if i attach a bunch of scripts all at once, im assuming they go into some kind of queue (or effectively so). It would be nice if there was an event (now called Messages) that was emitted when the last one finished loading (i.e whenever the queue transitions to an empty state from not being in an empty state). That way i can get rid the 69 hour time budget and listen for that event. Or maybe u have a better idea, lol.
Oh damn, which version are you updating from? I think AppTypeRegistry became a thing eons ago 😄
Or is this a macro change in bevy
I'm updating from Bevy 15.3 and BMS 0.14.0-alpha.1.
I see in your macro you reference TypeRegistry without qualification. I imagine this is so you don't have to commit to ::bevy::reflect::TypeRegistry or ::bevy_reflect::TypeRegistry.
Is a BMS version for 0.17 in the works? I did have a quick look at the codebase, and it looks like it's bloody massive, so I'm guessing it's going to take a while?
Yeah i think this was exactly it
I am gonna merge outstanding work and then start working on 0.17
It shouldn't take too long assuming no big changes to latest rust compiler internals
I am currently working on registered callbacks
It should make @untold wharf happy. You'll be able to fully use shared contexts, since the callbacks are "frozen" when you store them i believe
This will also let you register callbacks from within callbacks no problem, and the old "dynamic" or "loose" callbacks will still work
Tbh i have been on the "not shared" train for awhile and just got used to it 😅
im looking forward to this tho!
got this working:
function on_script_loaded()
register_callback("on_test", dynamic_on_test)
end
function dynamic_on_test()
register_callback("on_test_last", dynamic_on_test_last)
return "on test: I am dynamically registered from a normal callback!"
end
function dynamic_on_test_last()
return "on test last: I am dynamically registered from another dynamic callback!"
end
And, once the callbacks are stored, if the script is reloaded, with say a completely empty body, they still work 😄
this also works for core callbacks like on_script_loaded and registered callbacks take priority over free-standing ones, meaning you can register all your callbacks in on_script_loaded in a shared context setting, and this will work as expected
there is also no issue registering callbacks within callbacks, as functions are read only Arc function pointers, cloned per read
Awesome!!!
Wait is register_callback new and not available in the current release? I saw it in the docs 🙃
Its new, its not in the current release but i think its on main?
I thought I was going crazy trying to figure out why it wasn't working 
Im wondering what use cases u have for it 👀 for me i only suggested it cus it was annoying dealing with global functions in lua.
I didn't really have one, I was just messing around reading the docs, but one thing I was thinking of was having events fire from my animation controller.
Are there any stub files or similar to allow for autocompletion in scripts, for the types and globals that BMS exposes?
I think this was the last time this was mentioned, it doesnt appear to be a huge ask. Not sure how far along they are with this.
https://github.com/makspll/bevy_mod_scripting/issues/245 i think this is the issue for it
Given we have function and type metadata available via traits and the various registries, we should generate a lanugage agnostic "binding" content format, which can then be transpilled to...
Something to look forward too then
Thank you
i found the issue! was something strangeish.
If i already had a script component on an entity hook.entity,
world.commands().entity(hook.entity).insert(
bevy_mod_scripting::core::script::ScriptComponent::new(scripts),
);
Doesn't seem to add necessary stuff. If I remove the scriptcomponent beforehand, then it works!
world
.commands()
.entity(hook.entity)
.remove::<bevy_mod_scripting::core::script::ScriptComponent>();
world.commands().entity(hook.entity).insert(
bevy_mod_scripting::core::script::ScriptComponent::new(scripts),
);
so scripts were being loaded, they just werent being updated after initialization 🤔 might be something on my end tbh
Hmm, so you add a script component to an entity, before there are assets loaded?
Or maybe somehow the script component doesnt emit onAdd somehow
For now the workaround I've used is doing luau wrappers and a library for people to interact with.
It does make life easier to have autocomplete...
they def should be added at the point where im calling it. i think the diff is that i was re-inserting the script component instead of adding for the first time. maybe some hooks arent being called?
@old trail with the new updates im getting this warning for types i dont own: " WARN bevy_mod_scripting_bindings::globals::core: duplicate entry inside types global for type: ScalingMode. This can cause confusing issues for script users, use CoreScriptGlobalsPlugin.filter to filter out uneeded duplicate types."
In this case, i dont see how i can foresee which ones are not needed. Just cus i dont need it doesnt mean a future modder wont, so i wouldnt want to filter it out. Id prefer to just give them different names. I.e i could call one "ProjectionScalingMode". So instead of a filter, maybe a way for the BMS implementer to rename those types would make sense?
I guess my final thoughts are:
-its good theres a warning
-its not great that its being caused by BMS
-Maybe my method of resolving it is better than CoreScriptGlobalsPlugin.filter, in which case BMS itself could solve it with my proposed solution.
-whenever something is renamed like that, it should print an INFO log so ppl still know. INFO logs are fine, warnings are eww 🙂
I'm trying to move up to 0.16, did something happen to display_value_with_world? I can't seem to find it anywhere now
Nvm I just found the migration notes...
silly me
Now to figure out how to use the display_with_type_info
hmm, interesting, I'll have a look to see if there's something obvious happening
The warnings are an intermediate stage, I think ultimately I'll want to add crate namespaces into these globals
it's display but with access to the type registry 😄
FYI, I am currently overhauling reflection paths, I think relying on Bevy's is ultimately going to take a long time for us to get to a nice place:
- No more
_1etc. accessed for tuple structs and tuples,[1]does it for everything - Maps and Sets support
Still need to do maps and sets but the rest of the tests are passing here: https://github.com/makspll/bevy_mod_scripting/pull/491
aaand done, now you will be able to modify stuff through dictionaries, no need for map_get and you can even use non-primitive types:
local key_simple_type = construct(SimpleType, {
inner = "foo"
})
assert(resource.simple_type_map[key_simple_type],
"Expected 'bar', got " .. tostring(resource.simple_type_map[key_simple_type]))
you can't make mutable references into sets for some reason with bevy reflection, that's something that definitely has to be fixed upstream
This is a bit wishy washy, but after upgrading my game to BMS for Bevy 0.16 ([email protected]), I find that my callbacks are sometimes not firing. For me this manifests as me pressing the "New Campaign" button and sometimes the callback won't fire. So then I have to exit the half-loaded campaign state, return to the main menu and try again. Sometimes it works, sometimes it doesn't. Is this a known issue with the Bevy 0.16 upgrade?
Somewhat related, why do my callback handlers have to be registered in FixedUpdate, e.g.,
app.add_systems(
FixedUpdate,
(
event_handler::<ResumeCallback, LuaScriptingPlugin>,
event_handler::<MainCallback, LuaScriptingPlugin>,
),
);
My question is wishy washy because it's pretty hard for me to provide much more info than this, but maybe it's a known issue? I'm still on [email protected] but I note that [email protected] doesn't list any "fixes" in its changelog so I've been hesitant upgrade because it doesn't appear to have any bugfixes.
Somewhat related, why do my callback handlers have to be registered in FixedUpdate
This seems like an odd question to me. You dont have to put it in FixedUpdate. How come you got it in there?
The example puts it there and also if I change it to Update, the callback never seems to fire (I can't get it to fire ever, whereas FixedUpdate it fires sometimes at least).
Ok, well i have never done this and i haven't run into your issue. I think the reason why FixedUpdate was used in this case was for the sake of implementing the game of life. In the docs for attaching scripts, the Update schedule is used in the example. So its definitely not required. It just comes down to how fast u want the events to be processed (every X seconds vs every frame).
The fact that its intermittent is weird, not sure how to debug that without the code in front of me.
Hmm right, well if you are using Update and as you suggest that GoL example used FixedUpdate for reasons specific to the game logic, maybe I need to look elsewhere. It definitely worked 100% of the time in the Bevy 0.15 BMS version which is why I wondered if anything could have changed in BMS itself for this version. And yeah, that's why my question is wishy washy, it's hard for me to get a minimal reproducing example to share.
(I'll try turning on BMS logs as well to see if I can see anything of interest)
OK I have traced it down to the recipients list being empty during the case when the campaign fails to load (and so the for loop just after is skipped entirely).
This is the system I use to send the event for the main callback.
fn start_script(
mut commands: Commands,
script_assets: Res<Assets<ScriptAsset>>,
mut script_callback_events: EventWriter<ScriptCallbackEvent>,
script: Option<Single<(Entity, &CampaignScript)>>,
) {
let Some((campaign_script_entity, campaign_script)) = script.map(|s| s.into_inner()) else {
error!("Could not find script");
return;
};
debug!("Starting script");
commands
.entity(campaign_script_entity)
.insert(ScriptComponent::new(vec![campaign_script.script.clone()]));
// Added this panic if the script asset is not loaded just to make sure
// the asset not being loaded is not the issue.
let _script_asset = script_assets
.get(&campaign_script.script)
.expect("Campaign script asset not loaded");
script_callback_events.write(ScriptCallbackEvent::new(
MainCallback,
vec![ScriptValue::Integer(campaign_script.step as i64)],
Recipients::ScriptEntity(campaign_script.script.id(), campaign_script_entity),
None,
));
}
So the recipient is a Recipients::ScriptEntity.
But I'm not too sure how to debug inside get_recipients further to see why the result from the match is empty (sometimes).
the recipients will be empty if the script is not loaded fully, one of the changes was that script loading might take multiple frames, you can opt out of this by disabling the load time budget
there is something funky going on that @rare badge is facing that I haven't been able to identify yet, todo with loads not triggering sometimes that they got around by re-inserting the script component, but I am not sure if this is something in BMS yet
I'm trying to run both client and server Lua environments in the same Bevy app. I'm using per-script context and want to provide different globals per environment. Currently I'm manually clearing the context initializers in LuaScriptingPlugin and setting the ones I need depending on the script being loaded. Is there a better way to do this?
In the future I'd also like to support modifying the world instance the client is provided, and for example filter query results and resource/asset access. I'm assuming I'll have to write manual bindings for his?
Right. Yeah I checked if the asset was loaded (see comment in code snippet above). Are you implying there’s another check I could do to make sure it’s fully loaded or are you saying this seems like a BMS bug that’s hard to track down and the best I can do is maybe wait some arbitrary number of frames?
The asset loaded != script loaded anynore
Because after the asset loads, the processing can be spread over multiple frames
The reason why didnt think it was a loading issue was because i had a feeling this was being done much later, based on what they said earlier. However, if thats the issue then the intermittency makes sense. And the fact it doesnt work at all in Update also makes sense. When u were using FixedUpdate, the delay it was causing would sometimes be enough for the script to fully load. But sometimes it wouldnt be enough. With Update ur only giving it 1 frame so it simply never loaded.
Is there a better way to do this?
This sounds like exactly the usecase for context initializers, but why are you clearing the existing ones ?
I'm assuming I'll have to write manual bindings for his?
Yes, there is no sandboxing built in at world access level
I don't want clients to have full mutable world access.
I see, this is something we could support, and in fact, script systems are entirely powered by SubsetAccessMap's which explicitly only allow access to a certain subset of the world: https://github.com/makspll/bevy_mod_scripting/blob/d67ca70efec1392083ab76cc3f1579c347b1235f/crates/bevy_mod_scripting_bindings/src/access_map.rs#L572, we'd need to expose this logic to the user somehow, as it sits in the world internals currently
this wouldn't require modifying/replacing any bindings, as they would be blocked at rust level
if you're looking to block off a small number of resources, you could maybe consider claiming accesses to those, but not releasing them, I think that gets reset after every handling
So do I need to add a function on_script_loaded() to my Lua and then inside that function, signal back to, say, some ECS resource/entity that the script is now fully loaded? Or is there another way I'm meant to detect "script loaded"?
I think u can listen to ScriptCallbackEvent and check the label to see if its equal to OnScriptLoaded? Would be nice if we had a more direct event to listen to.
idk if it would help but lmk if it would help to see the source code i have / diff from the upgrade
That doesn't seem to work. No such ScriptCallbackEvent event is fired for the OnScriptLoaded callback, whether I have a function on_script_loaded() in my Lua or not.
Yeah, on second look it looks like BMS isnt sending that Event at all. Thats annoying.
you will need to wait untill all the scripts you need loaded are loaded (or have failed to load), atm there are the following options:
ScriptContext<LuaScriptingPlugin>let's you iterate over all residents, these are all currently loaded scripts, you can also access that viaRecipients::get_recipients- listen to
ScriptCallbackResponseEvent, they're not emited by default for core callbacks, but you can enable this viaScriptingPlugin::emit_core_callback_responses, one per loaded script - check
ActiveMachines<LuaScriptingPlugin>::active_machines, this will tell you if there are any scripts being processed (loaded/reloaded) atm, once that's zero and you are not issuing more asset changes/attachments etc, then everything has been processed
Seeing as this is a bit rough atm, I am cooking a system parameter + rust example on a simple script loading bar using that
what I'll do is add a bit more trace/debug logs into the next version (pre 0.17), which should hopefully help pinpoint this behavior
@old trail my game runs at 15fps when i try to run with --release and tracy. Maybe u know whats ups? Using profile_with_tracy doesnt seem to change anything.
This should make loading slightly nicer: https://github.com/makspll/bevy_mod_scripting/pull/494
I guess BMS has a lot of profiling logs, you should be able to exclude those: https://stackoverflow.com/questions/76939805/tracing-how-to-filter-logs-under-specified-levels-for-layer
but are u getting the same issue?
Yeah profiling builds are much slower
oh nice result flattening is now stable in rust
gonna be honest, idk what im supposed to do 😅
try running with RUST_LOG=your_crate=trace,bevy_mod_scripting_core=error,bevy_mod_scripting_bindings=error
gonna assume this is an ENV variable
looks like it did nothing
how about RUST_LOG=none,your_crate=trace
nope
your_crate should be the name of my crate, right?
I do see warnings getting printed, so maybe its not applying somehow
hmm, you can also modify this via the bevy LogPlugin here:
https://github.com/bevyengine/bevy/blob/ca136908d1c5129e1542622111dfd69d367d07c6/crates/bevy_log/src/lib.rs#L294C21-L294C35
it might also be off instead of none
off in the ENV didnt work
but setting the LogPLugin worked:
.set(bevy::log::LogPlugin {
filter: "error".to_owned(),
level: bevy::log::Level::ERROR,
custom_layer: |_| None,
})
Thats kinda weird tho, cus before it was not spamming logs as f ar as i could see. Were there logs that were invisble but still taking up cpu time somehow?
it's the trace logs, they're not visible in console, but they do get sent over the wire to tracy
I see. I cant confirm it myself right now, but is disabling that going to stop tracy from doing its job? The whole point of this is to figure out why my game (or BMS or some other crate) is degrading performance rapidly as i play the game.
You'll want to enable the crates you want to investigate, but it will slow everything down, I'd start by adding profiling scopes where you suspect things are slow, then drill down into those areas enabling scopes selectively if full profiling is slow
Can u show me an example of adding a scope? Sorry i know we're not talking about ur crate anymore 😅
unless ur talking about the error levels
Like example::test::module::submodule=trace
Ohhh so it doesnt just have to be the whole crate, u can be very specific. I see! Its just a matter of figuring out which modules are available and what each of them do.
yeah exactly, i guess if you manage to capture a first trace without filters, it will make it easier to identify those you don't care about
Yeah i was worried that the performance hit from all the traces might make it difficult to parse the tracy output, though. Not sure if thats true. Still need to give it a try. Just need to get around to it.
sneak peek
Thats pretty cool! How easy is it to get this to work with our own APIs? So when we use NamespaceBuilder to register functions or when we use the script bindings macro in structs
really easilly, it will just work
Thats kinda wild, lol. What about comments?
yup those too get to declaration files
I dont have to manually pass it as a string or anything? It just yoinks it out of the source code?
Yeah, if you use the script bindings macro all comments get auto yoinked into docs, on the namespace builder you add them manually, but yeah
as someone who is worse than an 8 y/o on roblox at programming lua i am very excited for this
Thats pretty cool! And i bet there will be a way to improve the namespace builder to include comments in the same way. For example, currently i could just make a struct that has my API, and simply registering it with bevy makes it available with the script bindings macro. The only difference is not having access to the world, which might be addressable with let world = ThreadWorldContainer.try_get_world().unwrap();. I did a quick test and it seems to work? Either way, I think we're pretty close to a good solution there.
Me too haha
The macro uses the builder underneath, so everything possible via the macro should be doable via the builder AFAIK
I see. I guess what I had in mind is "Namespace via structs". That way we get the comments for free but still somehow maintain the same experience. Something like how Ref and Mut work, for example. I believe ur doing some kind of dependency injection there? Idk how that magic works lol, but it would be cool if u could make this an injectable parameter: ctx: FunctionCallContext as well. With that, i dont see myself needing to use the Namespace builder directly. it would just be ApiName.function() on the lua side. And we would only need it for associated functions, if doing it with methods (functions with self) makes it tricky, then we can ignore that case.
Okay, that is very noice! I'm going to have to see how easy it is to use that in luau
Is this what you mean? https://github.com/makspll/bevy_mod_scripting/blob/main/crates%2Fbevy_mod_scripting_functions%2Fsrc%2Fcore.rs#L94
If so its already possible, and all the macro is doing is passing stuff into the same builder
The injection happens via traits
Ok at first i was confused, like maybe we weren't on the same page. So i just tried it and it worked 🤣 sorry! Thats awesome!
alrighty, almost there: https://github.com/makspll/bevy_mod_scripting/pull/497
I'll be able to go back to things soon, I'll be looking forward to that!
Awesome. Really eager to move on to 0.17 😅 But I also cant wait for the intellisense to start working better in lua world. Productivity should be a lot better.
@old trail so about a week ago i noticed that some things stopped being logged (i.e things are crashing silently), like when theres an internal issue when i use world.get_component or construct. Im not using any log custom log filters (and other warnings and errors do show up anyway, so its a specific kind of error)
An example of an error that does this: using construct and providing a field that doesnt exist. Completely silent error.
That's next on the roadmap, I've started on the PR, it doesn't look too bad rn
Won't the field just get ignored ?
Id love to debug it, but i need the errors first haha. For example this doesnt work construct(types.Node, {}) and i figured just the defaults would be used, but i guess not. Dunno why.
This doesnt work either (cus _1 doesnt work anymore). The error there might be because it was missing a required field, again idk cus its silently failing lol.
local projection = construct(types.Projection, {
variant = "Orthographic",
_1 = ortho_proj
})
_1 still works just without the underscore
yeah i know, just showing u an example of it failing 🙂
Why do you think it's failing?
I am pretty sure the function will just ignore things not on the type
the function doesnt continue after that, like if i put a print after it wont print
Strange, errors are definitely propagated to the function call and logged from there
iight i wont want to waste any more of your time on this, but if u use construct and then misspell the type name (i.e if u did types.Projdection) does fail as expected? If so, dont worry about it for now. Il do some more digging later.
I think i know what might be happening I'll check tomorrow: https://github.com/makspll/bevy_mod_scripting/blob/main/crates%2Fbevy_mod_scripting_core%2Fsrc%2Fpipeline%2Fmachines.rs#L283
I think should send an error event
Actually nvm
It does later
I just put a print statement there and it didnt go off. From what i can tell, im getting to that block but that condition is never met
Wait a min, is this in regards to script loading? In my case im talking about callbacks. Not entirely sure if this ScriptMachine business deals with that.
Any examples/pointers on how I can do something like the below (global function, not using the standard NamespaceBuilder, with world access)?
context.globals()
.set(
"func",
LuaScriptValue::from(ScriptValue::Function(|key: String, value: ScriptValue| {
let world = ...
})),
)
So u want access to world in there is what ur trying to figure out? try doing this: ThreadWorldContainer.try_get_context().unwrap().world
That, and construct ScriptValue::Function.
FYI: This works but the docs dont really cover it:
construct(types.LineHeight, {
variant = "RelativeToFont",
["1"] = 1
})
FYI bevy 0.17 is pretty much there, I am just passing tests and expanding on the existing bindings: https://github.com/makspll/bevy_mod_scripting/pull/498
@old trail should these bevy dependencies still be on 0.16?
https://github.com/makspll/bevy_mod_scripting/blob/main/Cargo.toml#L143
oh i see, my bad. I thought those latest commits were it.
@old trail so uh, finally got around to actually trying the 0.17 branch but im getting a "unresolved import bevy_mod_scripting_bindings::function::glue
could not find glue in function" for the safe_transmute function.
I'm writing a Nano-9 plugin nano9_ink to access inkle stories from Lua. Here's a toot that demos it. But I realize it actually has no requirement for Nano-9. It's actually just a BMS plugin. So I'm thinking maybe I ought to rename this project to bms_ink. Thoughts?
A Pico-8 compatibility layer for Bevy. Contribute to shanecelis/nano-9 development by creating an account on GitHub.
uhh, i guess u already renamed it cus the link broke 😅
Sorry. Try it again. It was marked private accidentally.
is it possible to use bevy_mod_scripting in a shared library? i'm building my game's simulation as an mlua module that's loaded by a larger lua program.
it would be nice to have bevy_mod_scripting ease the communication between the two
It works now but there is no readme (which is fine if u didnt intend there to be one)
Apologies. It's not ready for release. I just wanted to settle on a name first. @old trail, how do you feel about crates using a "bms_" prefix?
Oh did you pin the commit?
Very cool, i wouldnt wish the bevy_mod_scripting prefix on anyone 😂 bms is probs best, and on this topic i was considering renaming the crates to something snappier, but i am not very creative and the current name has great SEO properties
You mean to "import" the stuff bms provides outside of the bevy runtime? The way its designed rn nothing will work unless the world pointer is injected and due to safety it has to happen every time a script is run, so the entry point kinda has to be a bevy system
If ur asking to confirm which version im on, i should be on d968ff9 from the feat/bevy-0.17 branch.
Also, hope ur doing ok man. Its been a hot min 🙂
I just realized the reason why u asked. I didnt read the PR 😅 but on 42d87a0 im getting "failed to resolve: could not find glam in the list of imported crates" in like 133 places
i refactored it to use the bevy_math type as a temp fix.
Hmm I managed to compile it with this setup:
bevy = "0.17"
bevy_mod_scripting = { git = "https://github.com/makspll/bevy_mod_scripting/", rev = "d9501a0", features = [
"lua54",
] }
I think I mentioned one commit later than I meant in the PR but this one seems to work
oh i can try it in a min
Yeah, that works for me without the temp fix. Thanks 👍
uhh, im getting a runtime error that I didnt get before, tho
Error in dynamic script system `my_system`: WithContext(
stack traceback:
[C]: in function '__index'
[string "/home/peepo/rust/bevy/bevy_mod_scripting/crat..."]:45: in function <[string "/home/peepo/rust/bevy/bevy_mod_scripting/crat..."]:28>,
ReflectionPathError {
error: Cannot reflect into tuple truct of type: bevy_transform::components::global_transform::GlobalTransform with string key: `["translation"]`,
reflected: Some(
ReflectReference {
base: ReflectBaseType {
type_id: TypeId(
"bevy_transform::components::global_transform::GlobalTransform",
),
base_id: Component(
105v0,
ComponentId(
Unregistered ComponentId - ComponentId(97),
),
),
},
reflect_path: ReferencePath {
one_indexed: true,
path: [
StringAccess(
translation,
),
],
},
},
),
},
)
thats from calling :translation() on a : GlobalTransform(Affine3A { matrix3: Mat3A { x_axis: Vec3A { x: 0.6, y: 0.0, z: 0.0 }, y_axis: Vec3A { x: 0.0, y: 0.6, z: 0.0 }, z_axis: Vec3A { x: 0.0, y: 0.0, z: 0.6 } }, translation: Vec3A { x: -76.4, y: -415.0, z: 1.0 } })
Oh that's because that method seems to be missing
it's probably because I now filter out types which don't have public import paths, gonna try something
ohh right yeah I did fix that in 42d87a0 but prolly included something else
yeah thats what i figured
aight I've split out some of the fixes I've made in later commits from my holiday stash I was working on, c18ae77 should get you there I can see the 'translation' function there and it's building for me
Unfortunately I can't merge until I fix tests, which requires improved bindings which requires quite a big step up in how we codegen, on the plus side once that's done that's gonna get much more comprehensive
Also, hope ur doing ok man. Its been a hot min 🙂
I am doing great thanks! The codegen work is a bit involved and slowing down towards the holidays 😄
Hey @Gedes, just wanted to let you know you got a new alpha tester for your BMS branch "feat/bevy-0.17-part-2": I updated Nano-9 to Bevy 0.17 tonight using your branch and so far things seem to be working fine.
nice! That's good to know, FYI that branch is definitely WIP (on the bindings side things will be moving around), I will be trying to merge the first part today though as I see bevy 0.18 is round the corner (things are moving fast haha!)
Would be great if we got on 0.18 within a reasonable time 😆 but no pressure 
BMS's Bevy 0.17 branch got merged in! Gedes throwing down +24k -11k LOC.
@old trail Once again i gotta thank you for your hard work on this project. Really means a lot!
I finally got bevy_minibuffer updated for Bevy 0.17. Just in time for Bevy 0.18, which is around the corner. Maybe when BMS 0.15 is ready, maybe Nano-9 will be ready for a real 0.1.0 release. We're just late bloomers.
I think migrating BMS to 0.18 will be ez.
I had not seen that... I've been away for a but... But absolutely phenomenal work...
Alrighty, I am finally gonna write those release notes and announce the work so far and start cracking on bevy 0.18
Damn I forgot how much good stuff was added last few months
@old trail I don't think this really affects me (I'm just curious) but why is BMS rust toolchain only on Rust 1.89?
Its the minimum bump needed to make 0.18 bevy work, larger bumps may entail updating the nightly version used in codegen which will mean following internal compiler changes in our compiler plugin. Avoiding that means a much quicker bump.
It should build fine with higher toolchain versions anyway
Can confirm, i been using nightly the entire time i been using BMS 🙂
@old trail upgrading to Bevy 0.18 (and BMS 0.19 via main/7401da44), it looks like the following error could be related/caused by BMS. I don't have a repro, but just wondered if anything stands out on BMS side? #ecs message
Attempted to update the location of a despawned entity, which is impossible. This was the result of performing an operation on this EntityWorldMut that queued a despawn command
I’m not sure 😬
@old trail your update logs in the book are a godsend, it's making me less scared of moving from 0.15.1 to 0.19
fn message_spawn(
mut reader: MessageReader<OnSpawn>,
mut writer: MessageWriter<ScriptCallbackEvent>,
query: Query<&ScriptComponent, With<Enemy>>,
) {
for message in reader.read() {
let script = query.get(message.entity).unwrap().0.first().unwrap();
println!("Sending message for ID: {}", message.entity.index_u32());
writer.write(ScriptCallbackEvent::new(
UnitSpawn,
vec![
ScriptValue::Integer(message.entity.index_u32() as i64),
],
Recipients::ScriptEntity(script.clone(), message.entity),
Some(Language::Lua)
));
}
}
Would this be an "idiomatic" way to fire an event to a specific entity's script component? I am firing a message whenever a unit is spawned and acting on that message in a few places, one of these is executing a script's method
Going up to bevy 0.16->0.18 was quite smooth except me being silly. Thank you again for the good documentation and all...
am i able to access the Time resource from a script?
this is giving an error:
local MovementAcceleration = world.get_type_by_name("MovementAcceleration")
local LinearVelocity = world.get_type_by_name("LinearVelocity")
local Time = world.get_type_by_name("Time")
function on_movement_action(action)
local variant_name = action:variant_name()
print(variant_name)
if variant_name == "Move" then
local move_dir = action[1]
local acceleration = world.get_component(entity, MovementAcceleration)[1]
local velocity = world.get_component(entity, LinearVelocity)[1]
local delta_time = world.get_resource(Time):delta_secs()
velocity.x = move_dir.x * acceleration * delta_time
velocity.y = move_dir.y * acceleration * delta_time
end
end
and i registered the type.
Error:
Error in language: Lua, in script: scripts/player.lua, at line: 17
in function get_resource on Namespace for type World:
Error converting argument 1: Value type mismatch: expected bevy_mod_scripting_bindings::query::ScriptResourceRegistration, got ()
Context:
stack traceback:
[C]: in field 'get_resource'
[string "/home/storm/.cargo/registry/src/index.crates...."]:17: in function 'on_movement_action'
callback: on_movement_action
args: [Move(Vec2 { x: 1.0, y: 0.0 })]
Language: Lua
^ still curious if i can, but i just did it a different way where i passed delta_secs as an argument.
Hello! I've been doing some experimenting with BMS lately and I was wondering if there was an API for inserting resources from a script? I though the world global would have insert_resource but it seems not.
I think you need to world.get_type_by_name("Time<Real")
same problem but it's fine
ty <3
Oop, forgot the closing >
hmm oki ill try that
ah thank you <3
Yeah, that's right, you either:
- create event handlers, subscribe to them in scripts and send these events
- register callbacks from scripts and invoke them from bevy by retrieving the callback yourself
- create a script system which runs on some schedule
Nice! that's good to hear, writing that is probably my least favourite bit of releasing, so it's good to hear people use these!
This worked for me:
Hmm seems not atm, but you can definitely write that API yourself via bindings, I am sure there was a good reason it doesn't exist atm, I'll revisit that
with BMS systems that use queries, is there an equivalent for .get() yet? To get the components corresponding to a given entity without doing a linear scan on everything the query picked up
Also have some other questions:
- While testing script systems I ran into multiple different ways to crash the game, is this also an issue when not using systems?
- Is there a way to prevent callbacks from running for an entity that has already despawned?
- Are there any better ways to attach scripts to entities from the scripting side? Systems would be convenient since they can automatically pick up entities by their components, but is there a way to e.g. "extend" a given component with a script?
for your second question: i think it should do that on its own. i tested by despawning my player entity which had a script printing every frame, and it stopped correctly
I definitely had an instance where an entity had already despawned when its callback hit; it caused the script to throw an error trying to access its components
atm, all the bult in query interfaces go through this flow: https://github.com/makspll/bevy_mod_scripting/blob/d1f53048056a475ed21eef8ed571b834274e40d7/crates/bevy_mod_scripting_bindings/src/query.rs#L336, so not atm, but it doesn't seem hard to add
In fact this might work (it compiles) in a binding of your own, to be used like world:query():component(ComponentA):query_get(MyEntity)
#[script_bindings(name = "query_functions", remote)]
impl ScriptQueryBuilder {
pub fn query_get(
ctxt: FunctionCallContext,
query: Val<ScriptQueryBuilder>,
entity: Val<Entity>,
) -> Result<Val<ScriptQueryResult>, InteropError> {
let world = ctxt.world()?;
let query = query.into_inner();
let result: Val<ScriptQueryResult> = world
.with_global_access(|world| {
let mut built_query = query.as_query_state::<EntityRef>(world);
let query_result = built_query
.get(world, entity.into_inner())
.map_err(InteropError::external)?;
let references: Vec<ReflectReference> = query
.components
.iter()
.map(|c| {
ReflectReference::new_component_ref_by_id(
query_result.id(),
c.component_id(),
c.type_registration().type_id(),
)
})
.collect();
Ok(Val(ScriptQueryResult {
entity: query_result.id(),
components: references,
}))
})
.flatten()?;
Ok(result)
}
}
While testing script systems I ran into multiple different ways to crash the game, is this also an issue when not using systems?
I'd say system scripts are an experimental part of bms still, I believe they were built before fallible systems in bevy hence why they are going to be prone to crashing. The "vanilla" parts of BMS should be hard as hell to crash with
Is there a way to prevent callbacks from running for an entity that has already despawned?
Callbacks only run on "loaded" and"active""attached" contexts within the framework, and since despawning an entity triggers an "unload", callbacks should not trigger on these. We have a bunch of lifecycle tests that verify this here: https://github.com/makspll/bevy_mod_scripting/blob/1391f0d0615a89f391ea55e7b168a6288168e8dd/assets/tests/lifecycle/default/entity_script/loading/scenario.txt, I wonder if maybe this is due to unloading now potentially taking more than one frame to complete, see: https://makspll.github.io/bevy_mod_scripting/ScriptPipeline/pipeline.html#timing--order. Would be keen to hear how you managed to see those errors!
Wait there is an active status on scripts ?
I might need to use that !
Are there any better ways to attach scripts to entities from the scripting side? Systems would be convenient since they can automatically pick up entities by their components, but is there a way to e.g. "extend" a given component with a script?
You can createScriptAttachment's via scripts directly https://makspll.github.io/bevy_mod_scripting/core_bindings/types/scriptattachment.html, and you can create references to script assets viaReflectReference
So given bindings that load and give you the handle to a script asset, you can directly add a ScriptComponent to an entity as normal
Not quite! Sorry I think a better word is "attached"
FYI, I am currently in the middle of writing an open source game that will use BMS as both:
- a large example
- a tech demo
- a way for me to gain some perspective and pick the right way forward in improving the framework
- actually figure out the idiomatic patterns in bevy
Is it public yet?
Yeah, it's in very early stages but available here: https://github.com/makspll/arcane_assembly
I'll take a look at it.
I am also in the middle of doing some sort of tech demo which I plan to be open source once once I get it where I want.
I would be happy to use it as an example too if needed.
That would be amazing, I think BMS is fairly hard to grasp at the beginning so the more real examples the better!
Although I use BMS to interact with rust compiled game logic, not for entity attached scripting.
I do have a save setup that saves lua data.
which might be interesting for people.
(as in my whole scenario state)
Yeah even better!
I can give you access to that in private if you so wish to take a look at it.
Sure, can't hurt!
(sent a dm)
Any specific reason for your choice of physics engine? I'm on avian and not rapier.
Mostly since rapier seems more established and it had a ready to go character controller, didn't give it too much thought
Makes sense!
I went for Avian because it had a lightyear integration example at the time
in my case I just had an entity with a callback that runs once per FixedUpdate (event listener in FixedPostUpdate) that errored once after the entity despawned. My solution was to just do a world.has_entity(entity) check and early exit if it fails, but that's annoying and inconvenient.
yes it is 😭
pretty cool when u get used to tho :3
Was this ever resolved? Seems to still be the case that you can't use nil value for Option typed fields using Bevy 0.18 and BMS 0.19.1
In my case I just forgot to use ReflectDefault for the type in question.
Hmm I suspect there's something that I'm not understanding properly.
Let's say I have
#[derive(Reflect, Default, GetTypeDypendencies)]
struct MyStruct {
pub foo: String,
pub bar: Option<String>
}
construct(types.MyStruct, { foo = "Foo" } -- External error: field missing and no default provided: 'bar'
construct(types.MyStruct, { foo = "Foo", bar = nil } -- External error: field missing and no default provided: 'bar'
construct(types.MyStruct, { foo = "Foo", bar = "Bar" } -- Works
I've even tried
construct(types.MyStruct, { foo = "Foo", bar = construct(types.Option, { variant = "None" })
but that doesn't work either.
I'm afraid I don't quite understand where ReflectDefault comes into play?
I believe you have to use #[reflect(Default)], i.e i found from an example:
#[derive(Debug, Default, Clone, Reflect, Component)]
#[reflect(Component, Default)]
pub struct LifeState {
pub cells: Vec<u8>,
}
Yours doesnt seem to be a Component so ignore that part. U just need Default.
Doesn't work either. Seems to boil down to this issue by @old trail about a year ago https://github.com/bevyengine/bevy/issues/18018. I guess I'll just bypass this for the time being. Thanks a lot for your help @untold wharf !
You can manually register the type if it doesnt have a default type data btw
There's a new Lua implementation in pure Rust rilua, and I mentioned how we'd all like to be able to use bevy_mod_scripting with wasm, and how it seemed like his project might help facilitate that. Well, not only is he agreeable but he may do some of the integration work himself, which would be swell. So maybe we'll see a PR in the near future.
-# ↩ Daniel S. Reichenbach
@danielsreichenbach Well, my aim would be to get it supported by bevy_mod_scripting, which is what I use in Nano-9. I really want Nano-9 games to be able to run in wasm so that it has closer feature parity to Pico-8.
Is that the PR you were looking for, it integrates BMS apparently?
https://github.com/wowemulation-dev/rilua/pull/30
He has his lua lib working in WASM today.
Looks very cool! Would be curious how the benchmarks look, but if we have a wasm lua runtime we can run tests as examples in the book which is cool
Hadn't thought about that! That would be dope !
Came here because of rilua, am working on a fork of this project in https://github.com/danielsreichenbach/bevy_mod_scripting to add rilua backend but far from finished
I haven't done much benchmarking, currently I only test the Lua 5.1 test suite against rilua and try to match their speed.
Welcome! We're excited to see what you've been working on.
Probably gonna be a few more days before it's usable. I'm trying to get bevy fully running with this plus recast for navigation meshes, that's my local scenario. (https://github.com/wowemulation-dev/recast-rs)
I am starting some work on ironing out edge cases in the scripting pipeline: https://github.com/makspll/bevy_mod_scripting/pull/523, should help with your problem @olive drift among some other ones (will need to do add some async support to fully test tho)
neat!
Oh, wasm is implementing exceptions, which might mean mlua will get a wasm target: https://github.com/mlua-rs/mlua/issues/23#issuecomment-3945105409
So that would mean luau as well right ?
From what i remember it would be lua53 or one of those
I am trying to wrap my head around potential usage paterns around async callbacks,
How do people think they should work?
Lets say we have a callback async fn do_time_consuming_thing,
It is triggered by a one off event and script begins executing it over many frames.
Id be surprised if that allowed us to execute any other callbacks for the duration of the processing, since this future will be referencing the script context, or at least a part of it. So the whole context would be out of action for a few frames and not receive any other callbacks ( and if its possible to drive multiple parallel callbacks against one context, would you want this allowed ? Sounds messy )
Does that sound useful? Would we want to allow core (on script loaded etc) callbacks be executed asynchronously too?
I am kind of mostly imagining async callbacks as a way to smooth out the framerate when bigger processing is happening within scripts / sandboxing somewhat
In my case I was having frozen frames because of silly inneficient Rejection Sampling Method for random positions in a circle.
I would have potentially use for it for functions that create things as a one off.
However for fixed updated stuff I probably don't want it to spill over. Or at least I'd want a warning when that happens.
Maybe I'd want it to be used for macro ai stuff like managing a whole faction ?
But it would likely require the compatibility with fixed update in some sort of dual thread like thing ?
Perhaps the way to do it is to run the function as a lua coroutine ?
That way if the user knows their function is going to take a while they can yield when needed.
https://wowemulation-dev.github.io/rilua/ is also available now for trying rilua.
That is sexy!
I hope it will be useful. Its gonna be updated every time I publish a new release.
I mean for wasm builds that would be amazing honestly!
Although i've stuck muself to using luau for types and won't be able to use it xD
Haha, that's fine. I am mostly active in World of Warcraft Emulation so rilua is rather niche I think 😅
Gedes was thinking of using rilua to create online example for BMS, so it would really facilitate understanding how BMS works which i think is smart.
Oof, async is a tricky thing to absorb. I'd suggest looking at bevy_defer. It's got the most comprehensive and practical async in Bevy. Personally I would try to avoid async in BMS. I'd suggest some kind of bevy_defer_bms that made async available that way.
interesting, bevy_defer does a few similar things w.r.t. accessing the world dynamically:
- dynamic ecs access validation: https://github.com/mintlu8/bevy_defer/blob/ca924b62c9dd2ae87ad0e316f15263f1c61aca11/src/access/dyn_access.rs#L14
- thread local world pointer: https://github.com/mintlu8/bevy_defer/blob/ca924b62c9dd2ae87ad0e316f15263f1c61aca11/src/executor.rs#L11
wonder if we could re-use some of this stuff instead in general
BMS uses the world pointer (UnsafeWorldCell) though instead, whereas bevy_defer "yoinks" the world within the tread scope: https://github.com/mintlu8/bevy_defer/blob/ca924b62c9dd2ae87ad0e316f15263f1c61aca11/src/executor.rs#L78
Oh I guess the difference is they use generics whereas we need to use ComponentID at runtime etc
One thing I am considering is the "async" within our context could be limited to the handler lifecycle, i.e. instead of thinking about spawning a background thread, instead we're "alotting dedicated time" for coroutines within the handler, essentially splitting the execution of a callback across multiple frames, but always with exclusive world access and in the same point in the schedule
or maybe even just allowing the initial load of a script to be async (as that already happens in a multi-frame context)
although I can see how having the world change in between yield points could be very confusing
but I think I agree, the more I am thinking about the ergonomics and amount of possible surprises, the more I think it's perhaps best to force people to implement their own async abstractions and script API's into those, while expecting callbacks to be cpu bound
I have exactly one async-y API from Pico-8 that I may or may not implement in Nano-9, and that's flip(). In Pico-8 it flips the frame buffers so that you can write a tight loop in Lua without having to always be called via _draw(). In Nano-9 to implement it, I believe I'd have to do my Lua calls within a coroutine and have flip() call yield unless there is some other way to suspend Lua's execution that I don't know about from the runtime. It's doable with coroutines but it complicates everything if I want to support it in general.
made an extension for writing bms integration tests!
Extension for Visual Studio Code - Syntax highlighting and completions for Scenario DSL from the bevy mod scripting framework
Oh god I need to get there soon...
I also need to export all my typing too...
Is there documentation about those DSL .scenario files?
they're currently only used internally, but I plan on making them usable by external crates + extensible too, so not many docs atm
If there are angles where I can contribute for docs and such I would be happy to
Developing BMS docs on how these work could be useful to help contributors
ATM I am looking at making ScriptValue::Tuple or more first-class multiple value returns
How did you end up bypassing this? Did you figure out how to do it?
I am having the same problem with this struct:
#[derive(Component, Reflect, Default)]
#[reflect(Component, Default)]
pub struct ForceMoveAi {
pub target: Option<Vec2>,
pub angle: Option<f32>,
}
Unfortunately I didn't. I realized that I didn't have any proper usage for scripting in that context so I replaced BMS with plain RON files for defining what I was trying to define in the script.
I think its a case of:
type_registry.register_type_data::<Option<Vec2>,ReflectDefault>()
Cant remember the exact syntac
Oh right @old trail and @vast dock now I remember! I did get it working as Gedes described, you have to manually register Option<ForceMoveAi>’s ReflectDefault.
Variadics are here!
https://github.com/makspll/bevy_mod_scripting/pull/527
Interesting: https://github.com/bevyengine/bevy/pull/21798
Objective
Tools using bevy_remote will be able to identify (via the schema) and trigger events.
Document bevy_ecs/src/reflect/event.rs
Solution
I've added a method world.trigger_event, a...
Is that a way to trigger events using reflection and therefore in lua?
Yup
👀 That an interesting find indeed!
@old trail do you have a mechanism setup for lua code coverage in BMS?
Hmm, no but this looks nifty: https://github.com/lunarmodules/luacov
Yeah I saw that!
I am currently setting up code coverage for my project.
Hence me working on it.
I will need to work on how to run test on lua mod/plugins in the context of what i'm doing.
But that is a problem for later me, the rust codebase is a start.
I am planning on trying to create mods in a similar way to factorio.
I am assuming they did their work right and I would like to try and include tests within mod folders.
But I have a lot of work to do to learn their dev environment.
Hey, I want to override the require method for a Lua context. tl;dr: load script from a specific directory depending on which Lua context is running a script. I'm currently doing this in Lua by overloading the require function and adding the static paths to the requested file, but I want to define this function in rust. Is this possible?
I dont see why you wouldnt be able to do that in mlua, youd be able to hook into the lua context in a context initializer or in a loading pipeline transition listener. Youd probably want to retrieve the original require function and then override it before calling it inside the override
Unless mlua has a denylist on function names
Thanks! I'm running untrusted lua code, I don't want to allow them to override my lua definition/guards.
Hey! I see you've already been discussing how to make async work. I'm currently exploring something that would greatly benefit from async scripts as well and thought I'd give some input.
What I'm trying to do is implement procedural generation through scripts. Expectedly this isn't particularly fast, so running these scripts freezes the program, which is not acceptable in a game.
This particular task happens to have one major advantage: it doesn't really need access to the world at all. If I were to implement this directly in Rust I'd simply spawn a task in the compute pool, and then perform all of the world updates once the result is ready.
I'm basically looking for a solution to do pretty much exactly that but with a script. If the script doesn't get access to the world (and instead only, say, a CommandQueue) it should be much easier to execute it asynchronously.
That's a useful perspective @vestal trench!
async commands is something that might be introduced into bevy natively as well
perhaps scripts lifetimed entirely to an async task could be the way to go
There are certainly pros and cons, I just threw this out because it happens to match a specific use case I have, though I might say this use case could be common in games.
You can sort of implement async commands by using the CommandQueue type, requires polling the task and applying it manually tho. You could potentially hide this away inside the crate, in the same system that generates the script result events for example, but it's not a terrible amount of code to write yourself anyway.
Genuine curiosity, but is there a reason you would want to use Lua for procedural generation instead of rust? I would have assumed rust is the better option for the speed of the implementation?
Like most scripting it's a tradeoff between speed and flexibility.
Think about a game like Age of Empires for example. There are many different kinds of map presets available. Using scripts greatly simplifies creating and distributing more of them, both by devs and by users.
I'd probably only consider this for games where the generation is hidden behind a loading screen. If you need to generate large amounts of terrain on the fly like say Minecraft then scripting would probably be too slow.
Makes sense !
trying to add a system from script, don't really know what is happening here
Error in language: Lua, in script: scripts/main.lua, at line: 11
in function add_system on Namespace for type World:
Invariant broken: After adding the system, it was not found in the schedule, could not return a reference to it
Context:
stack traceback:
[C]: in field 'add_system'
[string "~/.local/share/cargo/registry/src/ind..."]:11: in main chunk
Language: Lua
main.lua is simply
local startup_schedule = world.get_schedule_by_name("Startup")
local script_attachment = ScriptAttachment.new_entity_script(entity, script_asset)
local startup_system = system_builder("startup", script_attachment)
-- fails here:
local added_system = world.add_system(
startup_schedule,
startup_system
)
function startup()
print("startup system ran")
end
I can print the first 3 things and see
ReflectSchedule { type_path: "bevy_app::main_schedule::Startup", label: Reflect(bevy_system_reflection::ReflectableScheduleLabel) }
EntityScript(17v0, Strong(Reflect(bevy_platform::sync::Arc<bevy_asset::handle::StrongHandle>)))
Reflect(bevy_mod_scripting_core::script_system::ScriptSystemBuilder)
Try adding the system in a callback
Not directly in the script body
Not sure why that ugly error is hitting though
I moved it to on_script_loaded, same error unfortunately
I'm copying script loading code from one of the examples, maybe I picked an incompatible one?
use bevy::prelude::*;
use bevy_mod_scripting::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(BMSPlugin)
.add_systems(
Startup,
move |asset_server: Res<AssetServer>, mut commands: Commands| {
let script = asset_server.load::<ScriptAsset>("scripts/main.lua");
commands.spawn(ScriptComponent(vec![script]));
},
)
.run();
}
or maybe adapted incorrectly?
oh no access to the entity in on_script_loaded
hmm so this test is passing for me on main:
function on_script_loaded()
local post_update_schedule = world.get_schedule_by_name("PostUpdate")
local test_system = post_update_schedule:get_system_by_name("on_test_post_update")
local script_attachment = ScriptAttachment.new_entity_script(entity, script_asset)
local system_a = world.add_system(
post_update_schedule,
system_builder("custom_system_a", script_attachment)
:after(test_system)
)
local system_b = world.add_system(
post_update_schedule,
system_builder("custom_system_b", script_attachment)
:after(test_system)
)
-- generate a schedule graph and verify it's what we expect
local dot_graph = post_update_schedule:render_dot()
local expected_dot_graph = [[
digraph {
-- schedule assertion ...
}
]]
assert_str_eq(dot_graph, expected_dot_graph, "Expected the schedule graph to match the expected graph")
end
statements in the top level of the script may be called at unexpected times
maybe they get called twice and the system already exists or something
ok funnily enough it also works for me at the top level
and even if i register the same system twice it doesn't error
weird
so I can confirm it's not because of this:
https://makspll.github.io/bevy_mod_scripting/ScriptingReference/core-callbacks.html#on_script_loaded
This callback will not have access to the entity variable, as when the script is being loaded it’s not attached to an entity yet.
Documentation for the Bevy Scripting library
I tried to recompile the packages without cranelift (as recommended in Getting Started)
no luck
changing cargo settings is a 51 years type little maneuver xD
is this relevant at all? I added tracing around the part that errors and it seems a "reached final state" trace happens after my script has failed to add a system?
2026-03-31T23:59:02.040427Z DEBUG bevy_mod_scripting_core::script_system: Adding script system 'startup' for script 'EntityScript(entity: 25v0, script: path scripts/main.lua)' to schedule 'Startup'
2026-03-31T23:59:02.040548Z DEBUG bevy_mod_scripting_core::script_system: (MY LOG) Attempted to add script system "startup" to schedule Startup
2026-03-31T23:59:02.040882Z DEBUG bevy_mod_scripting_core::script_system: (MY LOG) Now find the system:
2026-03-31T23:59:02.041262Z TRACE bevy_mod_scripting_core::pipeline::machines: Reached final state 'bevy_mod_scripting_core::pipeline::machines::LoadingCompleted'. For script EntityScript(entity: 25v0, script: path scripts/main.lua)
2026-03-31T23:59:02.083974Z ERROR bevy_mod_scripting_core::handler: Error in script: 'path scripts/main.lua' :