Skip to content

Commit 16f4652

Browse files
committed
migrate work on core improvements from feature/better-trait-system to bevy 0.15
1 parent 1dfe12e commit 16f4652

File tree

18 files changed

+5165
-415
lines changed

18 files changed

+5165
-415
lines changed

crates/bevy_mod_scripting_core/Cargo.toml

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,9 @@ doc_always = []
2121

2222

2323
[dependencies]
24-
bevy = { workspace = true, default-features = false, features = [
25-
"bevy_asset",
26-
"bevy_gltf",
27-
"bevy_animation",
28-
"bevy_core_pipeline",
29-
"bevy_ui",
30-
"bevy_pbr",
31-
"bevy_render",
32-
"bevy_text",
33-
"bevy_sprite",
34-
] }
35-
bevy_event_priority = { path = "../bevy_event_priority", version = "0.8.0-alpha.1" }
24+
bevy = { workspace = true, default-features = false, features = ["bevy_asset"] }
3625
thiserror = "1.0.31"
3726
paste = "1.0.7"
3827
parking_lot = "0.12.1"
39-
anyhow = "1.0.75"
28+
lockable = "0.0.8"
29+
smallvec = "1.11"
Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,85 @@
1-
use bevy::asset::Asset;
1+
use std::{
2+
borrow::Cow,
3+
path::{Path, PathBuf},
4+
};
25

3-
/// All code assets share this common interface.
4-
/// When adding a new code asset don't forget to implement asset loading
5-
/// and inserting appropriate systems when registering with the app
6-
pub trait CodeAsset: Asset {
7-
fn bytes(&self) -> &[u8];
6+
use bevy::{
7+
asset::{Asset, AssetLoader, AsyncReadExt},
8+
ecs::system::Resource,
9+
reflect::TypePath,
10+
utils::BoxedFuture,
11+
};
12+
13+
use crate::{prelude::ScriptError, script::ScriptId};
14+
15+
/// Represents a script loaded into memory as an asset
16+
#[derive(Asset, TypePath)]
17+
pub struct ScriptAsset {
18+
pub content: Box<[u8]>,
19+
/// The virtual filesystem path of the asset, used to map to the script Id for asset backed scripts
20+
pub asset_path: PathBuf,
21+
pub language: Cow<'static, str>,
22+
}
23+
24+
pub struct ScriptAssetLoader {
25+
/// Used to set the language of the script
26+
pub language: Cow<'static, str>,
27+
/// The file extensions this loader should handle
28+
pub extensions: &'static [&'static str],
29+
/// preprocessor to run on the script before saving the content to an asset
30+
pub preprocessor: Option<Box<dyn Fn(&mut [u8]) -> Result<(), ScriptError> + Send + Sync>>,
31+
}
32+
33+
impl AssetLoader for ScriptAssetLoader {
34+
type Asset = ScriptAsset;
35+
36+
type Settings = ();
37+
38+
type Error = ScriptError;
39+
40+
async fn load(
41+
&self,
42+
reader: &mut dyn bevy::asset::io::Reader,
43+
_settings: &Self::Settings,
44+
load_context: &mut bevy::asset::LoadContext<'_>,
45+
) -> Result<Self::Asset, Self::Error> {
46+
let mut content = Vec::new();
47+
reader.read_to_end(&mut content).await.map_err(|e| {
48+
ScriptError::new_lifecycle_error(e).with_context(load_context.asset_path())
49+
})?;
50+
if let Some(processor) = &self.preprocessor {
51+
processor(&mut content)?;
52+
}
53+
let asset = ScriptAsset {
54+
content: content.into_boxed_slice(),
55+
asset_path: load_context.path().to_owned(),
56+
language: self.language.clone(),
57+
};
58+
Ok(asset)
59+
}
60+
61+
fn extensions(&self) -> &[&str] {
62+
self.extensions
63+
}
64+
}
65+
66+
#[derive(Clone, Copy, Resource)]
67+
pub struct ScriptAssetSettings {
68+
pub script_id_mapper: AssetPathToScriptIdMapper,
69+
}
70+
71+
impl Default for ScriptAssetSettings {
72+
fn default() -> Self {
73+
Self {
74+
script_id_mapper: AssetPathToScriptIdMapper {
75+
map: (|path: &Path| path.to_string_lossy().into_owned().into()),
76+
},
77+
}
78+
}
79+
}
80+
81+
/// Strategy for mapping asset paths to script ids, by default this is the identity function
82+
#[derive(Clone, Copy)]
83+
pub struct AssetPathToScriptIdMapper {
84+
pub map: fn(&Path) -> ScriptId,
885
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use bevy::ecs::system::Resource;
2+
use bevy::reflect::{PartialReflect, Reflect};
3+
use std::any::TypeId;
4+
use std::cell::UnsafeCell;
5+
use std::collections::HashMap;
6+
use std::fmt::{Display, Formatter};
7+
use std::sync::Arc;
8+
9+
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
10+
pub struct ReflectAllocationId(pub(crate) usize);
11+
impl ReflectAllocationId {
12+
pub fn id(&self) -> usize {
13+
self.0
14+
}
15+
}
16+
17+
#[derive(Clone, Debug)]
18+
pub struct ReflectAllocation(pub(self) Arc<UnsafeCell<dyn PartialReflect>>);
19+
20+
unsafe impl Send for ReflectAllocation {}
21+
unsafe impl Sync for ReflectAllocation {}
22+
23+
impl ReflectAllocation {
24+
pub fn get_ptr(&self) -> *mut dyn PartialReflect {
25+
self.0.get()
26+
}
27+
pub fn new(value: Arc<UnsafeCell<dyn PartialReflect>>) -> Self {
28+
Self(value)
29+
}
30+
}
31+
32+
impl Display for ReflectAllocationId {
33+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34+
write!(f, "{}", self.0)
35+
}
36+
}
37+
38+
/// Allocator used to allocate and deallocate `dyn PartialReflect` values
39+
/// Used to be able to ensure we have a "common root" for values allocated outside the world.
40+
#[derive(Resource, Default)]
41+
pub struct ReflectAllocator {
42+
// TODO: experiment with object pools, sparse set etc.
43+
allocations: HashMap<ReflectAllocationId, ReflectAllocation>,
44+
types: HashMap<ReflectAllocationId, TypeId>,
45+
}
46+
47+
impl ReflectAllocator {
48+
/// Allocates a new [`Reflect`] value and returns an [`AllocationId`] which can be used to access it later
49+
pub fn allocate<T: PartialReflect>(
50+
&mut self,
51+
value: T,
52+
) -> (ReflectAllocationId, ReflectAllocation) {
53+
let id = ReflectAllocationId(self.allocations.len());
54+
let value = ReflectAllocation::new(Arc::new(UnsafeCell::new(value)));
55+
self.allocations.insert(id, value.clone());
56+
self.types.insert(id, TypeId::of::<T>());
57+
(id, value)
58+
}
59+
60+
pub fn get(&self, id: ReflectAllocationId) -> Option<ReflectAllocation> {
61+
self.allocations.get(&id).cloned()
62+
}
63+
64+
pub fn get_type_id(&self, id: ReflectAllocationId) -> Option<TypeId> {
65+
self.types.get(&id).cloned()
66+
}
67+
68+
pub fn get_mut(&self, id: ReflectAllocationId) -> Option<ReflectAllocation> {
69+
self.allocations.get(&id).cloned()
70+
}
71+
72+
/// Deallocates the [`PartialReflect`] value with the given [`AllocationId`]
73+
pub fn deallocate(&mut self, id: ReflectAllocationId) {
74+
self.allocations.remove(&id);
75+
}
76+
77+
/// Runs a garbage collection pass on the allocations, removing any allocations which have no more strong references
78+
/// Needs to be run periodically to prevent memory leaks
79+
pub fn clean_garbage_allocations(&mut self) {
80+
self.allocations.retain(|_, v| Arc::strong_count(&v.0) > 1);
81+
}
82+
}
83+
84+
#[cfg(test)]
85+
mod test {
86+
use super::*;
87+
88+
#[test]
89+
fn test_reflect_allocator() {
90+
let mut allocator = ReflectAllocator::default();
91+
let (id, val) = allocator.allocate(0);
92+
assert_eq!(allocator.allocations.len(), 1);
93+
drop(val);
94+
allocator.clean_garbage_allocations();
95+
assert_eq!(allocator.allocations.len(), 0);
96+
}
97+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub mod allocator;
2+
pub mod proxy;
3+
pub mod query;
4+
pub mod reference;
5+
pub mod world;
6+
7+
pub use {allocator::*, proxy::*, query::*, reference::*, world::*};

0 commit comments

Comments
 (0)