mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
azalea-ecs
This commit is contained in:
parent
4c6657fe40
commit
57d91b5220
20 changed files with 1705 additions and 74 deletions
83
Cargo.lock
generated
83
Cargo.lock
generated
|
@ -278,12 +278,11 @@ dependencies = [
|
|||
"azalea-chat",
|
||||
"azalea-core",
|
||||
"azalea-crypto",
|
||||
"azalea-ecs",
|
||||
"azalea-physics",
|
||||
"azalea-protocol",
|
||||
"azalea-registry",
|
||||
"azalea-world",
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
"bevy_time",
|
||||
"derive_more",
|
||||
"env_logger 0.9.3",
|
||||
|
@ -326,6 +325,28 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azalea-ecs"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"azalea-ecs-macros",
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
"futures",
|
||||
"iyes_loopless",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azalea-ecs-macros"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"toml 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azalea-language"
|
||||
version = "0.5.0"
|
||||
|
@ -540,7 +561,7 @@ checksum = "022bb69196deeea691b6997414af85bbd7f2b34a8914c4aa7a7ff4dfa44f7677"
|
|||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
"toml",
|
||||
"toml 0.5.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1661,6 +1682,15 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "nom8"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
|
@ -2238,6 +2268,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c68e921cef53841b8925c2abadd27c9b891d9613bdc43d6b823062866df38e8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
|
@ -2429,9 +2468,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.24.2"
|
||||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
|
||||
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
|
@ -2491,6 +2530,40 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f560bc7fb3eb31f5eee1340c68a2160cad39605b7b9c9ec32045ddbdee13b85"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "886f31a9b85b6182cabd4d8b07df3b451afcc216563748201490940d2a28ed36"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233d8716cdc5d20ec88a18a839edaf545edc71efa4a5ff700ef4a102c26cd8fa"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"nom8",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
|
|
|
@ -16,6 +16,7 @@ members = [
|
|||
"azalea-buf",
|
||||
"azalea-physics",
|
||||
"azalea-registry",
|
||||
"azalea-ecs",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
|
|
@ -16,12 +16,11 @@ azalea-block = {path = "../azalea-block", version = "0.5.0"}
|
|||
azalea-chat = {path = "../azalea-chat", version = "0.5.0"}
|
||||
azalea-core = {path = "../azalea-core", version = "0.5.0"}
|
||||
azalea-crypto = {path = "../azalea-crypto", version = "0.5.0"}
|
||||
azalea-ecs = {path = "../azalea-ecs", version = "0.5.0"}
|
||||
azalea-physics = {path = "../azalea-physics", version = "0.5.0"}
|
||||
azalea-protocol = {path = "../azalea-protocol", version = "0.5.0"}
|
||||
azalea-registry = {path = "../azalea-registry", version = "0.5.0"}
|
||||
azalea-world = {path = "../azalea-world", version = "0.5.0"}
|
||||
bevy_app = {version = "0.9.1", default-features = false}
|
||||
bevy_ecs = {version = "0.9.1", default-features = false}
|
||||
bevy_time = "0.9.1"
|
||||
derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]}
|
||||
futures = "0.3.25"
|
||||
|
|
|
@ -11,6 +11,12 @@ use crate::{
|
|||
|
||||
use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
|
||||
use azalea_chat::FormattedText;
|
||||
use azalea_ecs::{ecs::Ecs, TickPlugin};
|
||||
use azalea_ecs::{
|
||||
component::Component,
|
||||
schedule::{IntoSystemDescriptor, Schedule, Stage, SystemSet},
|
||||
App, Plugin,
|
||||
};
|
||||
use azalea_physics::PhysicsPlugin;
|
||||
use azalea_protocol::{
|
||||
connect::{Connection, ConnectionError},
|
||||
|
@ -33,16 +39,11 @@ use azalea_protocol::{
|
|||
resolver, ServerAddress,
|
||||
};
|
||||
use azalea_world::{entity::Entity, EntityPlugin, Local, PartialWorld, World, WorldContainer};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::{
|
||||
prelude::Component,
|
||||
schedule::{IntoSystemDescriptor, Schedule, Stage, SystemSet},
|
||||
};
|
||||
use bevy_time::TimePlugin;
|
||||
use iyes_loopless::prelude::*;
|
||||
use log::{debug, error};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::{collections::HashMap, fmt::Debug, io, net::SocketAddr, sync::Arc, time::Duration};
|
||||
use std::{collections::HashMap, fmt::Debug, io, net::SocketAddr, sync::Arc};
|
||||
use thiserror::Error;
|
||||
use tokio::{sync::mpsc, time};
|
||||
use uuid::Uuid;
|
||||
|
@ -68,7 +69,7 @@ pub struct Client {
|
|||
/// The entity component system. You probably don't need to access this
|
||||
/// directly. Note that if you're using a shared world (i.e. a swarm), this
|
||||
/// will contain all entities in all worlds.
|
||||
pub ecs: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
pub ecs: Arc<Mutex<Ecs>>,
|
||||
}
|
||||
|
||||
/// An error that happened while joining the server.
|
||||
|
@ -96,11 +97,7 @@ impl Client {
|
|||
/// Create a new client from the given GameProfile, Connection, and World.
|
||||
/// You should only use this if you want to change these fields from the
|
||||
/// defaults, otherwise use [`Client::join`].
|
||||
pub fn new(
|
||||
profile: GameProfile,
|
||||
entity: Entity,
|
||||
ecs: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
) -> Self {
|
||||
pub fn new(profile: GameProfile, entity: Entity, ecs: Arc<Mutex<Ecs>>) -> Self {
|
||||
Self {
|
||||
profile,
|
||||
// default our id to 0, it'll be set later
|
||||
|
@ -156,7 +153,7 @@ impl Client {
|
|||
/// Create a [`Client`] when you already have the ECS made with
|
||||
/// [`start_ecs`]. You'd usually want to use [`Self::join`] instead.
|
||||
pub async fn start_client(
|
||||
ecs_lock: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
ecs_lock: Arc<Mutex<Ecs>>,
|
||||
account: &Account,
|
||||
address: &ServerAddress,
|
||||
resolved_address: &SocketAddr,
|
||||
|
@ -351,13 +348,13 @@ impl Client {
|
|||
self.local_player_mut(&mut self.ecs.lock()).disconnect();
|
||||
}
|
||||
|
||||
pub fn local_player<'a>(&'a self, ecs: &'a mut bevy_ecs::world::World) -> &'a LocalPlayer {
|
||||
pub fn local_player<'a>(&'a self, ecs: &'a mut Ecs) -> &'a LocalPlayer {
|
||||
self.query::<&LocalPlayer>(ecs)
|
||||
}
|
||||
pub fn local_player_mut<'a>(
|
||||
&'a self,
|
||||
ecs: &'a mut bevy_ecs::world::World,
|
||||
) -> bevy_ecs::world::Mut<'a, LocalPlayer> {
|
||||
ecs: &'a mut Ecs,
|
||||
) -> azalea_ecs::ecs::Mut<'a, LocalPlayer> {
|
||||
self.query::<&mut LocalPlayer>(ecs)
|
||||
}
|
||||
|
||||
|
@ -447,7 +444,7 @@ impl Plugin for AzaleaPlugin {
|
|||
app.add_event::<StartWalkEvent>()
|
||||
.add_event::<StartSprintEvent>();
|
||||
|
||||
app.add_fixed_timestep(Duration::from_millis(50), "tick");
|
||||
app.add_plugin(TickPlugin);
|
||||
app.add_fixed_timestep_system_set(
|
||||
"tick",
|
||||
0,
|
||||
|
@ -500,7 +497,7 @@ pub fn start_ecs(
|
|||
app: App,
|
||||
run_schedule_receiver: mpsc::Receiver<()>,
|
||||
run_schedule_sender: mpsc::Sender<()>,
|
||||
) -> Arc<Mutex<bevy_ecs::world::World>> {
|
||||
) -> Arc<Mutex<Ecs>> {
|
||||
// all resources should have been added by now so we can take the ecs from the
|
||||
// app
|
||||
let ecs = Arc::new(Mutex::new(app.world));
|
||||
|
@ -516,7 +513,7 @@ pub fn start_ecs(
|
|||
}
|
||||
|
||||
async fn run_schedule_loop(
|
||||
ecs: Arc<Mutex<bevy_ecs::world::World>>,
|
||||
ecs: Arc<Mutex<Ecs>>,
|
||||
mut schedule: Schedule,
|
||||
mut run_schedule_receiver: mpsc::Receiver<()>,
|
||||
) {
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use azalea_ecs::component::Component;
|
||||
use azalea_ecs::ecs::Ecs;
|
||||
use azalea_ecs::query::{ROQueryItem, ReadOnlyWorldQuery, WorldQuery};
|
||||
use azalea_world::entity::Entity;
|
||||
use bevy_ecs::{
|
||||
prelude::Component,
|
||||
query::{ROQueryItem, ReadOnlyWorldQuery, WorldQuery},
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::Client;
|
||||
|
||||
impl Client {
|
||||
/// A convenience function for getting components of our player's entity.
|
||||
pub fn query<'w, Q: WorldQuery>(
|
||||
&self,
|
||||
ecs: &'w mut bevy_ecs::world::World,
|
||||
) -> <Q as WorldQuery>::Item<'w> {
|
||||
pub fn query<'w, Q: WorldQuery>(&self, ecs: &'w mut Ecs) -> <Q as WorldQuery>::Item<'w> {
|
||||
ecs.query::<Q>()
|
||||
.get_mut(ecs, self.entity)
|
||||
.expect("Our client is missing a required component.")
|
||||
|
@ -57,7 +53,7 @@ impl Client {
|
|||
}
|
||||
|
||||
pub trait EntityPredicate<Q: ReadOnlyWorldQuery, Filter: ReadOnlyWorldQuery> {
|
||||
fn find(&self, ecs_lock: Arc<Mutex<bevy_ecs::world::World>>) -> Option<Entity>;
|
||||
fn find(&self, ecs_lock: Arc<Mutex<Ecs>>) -> Option<Entity>;
|
||||
}
|
||||
impl<F, Q, Filter> EntityPredicate<(Q,), Filter> for F
|
||||
where
|
||||
|
@ -65,7 +61,7 @@ where
|
|||
Q: ReadOnlyWorldQuery,
|
||||
Filter: ReadOnlyWorldQuery,
|
||||
{
|
||||
fn find(&self, ecs_lock: Arc<Mutex<bevy_ecs::world::World>>) -> Option<Entity> {
|
||||
fn find(&self, ecs_lock: Arc<Mutex<Ecs>>) -> Option<Entity> {
|
||||
let mut ecs = ecs_lock.lock();
|
||||
let mut query = ecs.query_filtered::<(Entity, Q), Filter>();
|
||||
let entity = query.iter(&ecs).find(|(_, q)| (self)(q)).map(|(e, _)| e);
|
||||
|
@ -80,7 +76,7 @@ where
|
|||
// bool, Q1: ReadOnlyWorldQuery,
|
||||
// Q2: ReadOnlyWorldQuery,
|
||||
// {
|
||||
// fn find(&self, ecs: &mut bevy_ecs::world::World) -> Option<Entity> {
|
||||
// fn find(&self, ecs: &mut Ecs) -> Option<Entity> {
|
||||
// // (self)(query)
|
||||
// let mut query = ecs.query_filtered::<(Entity, Q1, Q2), ()>();
|
||||
// let entity = query
|
||||
|
|
|
@ -3,16 +3,17 @@
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
use azalea_ecs::{
|
||||
component::Component,
|
||||
event::EventReader,
|
||||
query::{Added, Changed},
|
||||
system::Query,
|
||||
Plugin,
|
||||
};
|
||||
use azalea_protocol::packets::game::{
|
||||
clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket, ClientboundGamePacket,
|
||||
};
|
||||
use azalea_world::entity::MinecraftEntityId;
|
||||
use bevy_app::Plugin;
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, EventReader},
|
||||
query::{Added, Changed},
|
||||
system::Query,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use iyes_loopless::prelude::*;
|
||||
use tokio::sync::mpsc;
|
||||
|
@ -84,7 +85,7 @@ pub struct LocalPlayerEvents(pub mpsc::UnboundedSender<Event>);
|
|||
|
||||
pub struct EventPlugin;
|
||||
impl Plugin for EventPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
fn build(&self, app: &mut azalea_ecs::App) {
|
||||
app.add_system(chat_listener)
|
||||
.add_system(login_listener)
|
||||
.add_system(init_listener)
|
||||
|
|
|
@ -24,7 +24,7 @@ pub mod ping;
|
|||
mod player;
|
||||
|
||||
pub use account::Account;
|
||||
pub use bevy_ecs;
|
||||
pub use azalea_ecs as ecs;
|
||||
pub use client::{init_ecs_app, start_ecs, ChatPacket, Client, ClientInformation, JoinError};
|
||||
pub use events::Event;
|
||||
pub use local_player::{GameProfileComponent, LocalPlayer};
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::{collections::HashMap, io, sync::Arc};
|
|||
|
||||
use azalea_auth::game_profile::GameProfile;
|
||||
use azalea_core::{ChunkPos, ResourceLocation};
|
||||
use azalea_ecs::component::Component;
|
||||
use azalea_protocol::packets::game::ServerboundGamePacket;
|
||||
use azalea_world::{
|
||||
entity::{self, Dead, Entity},
|
||||
|
@ -117,7 +118,7 @@ impl LocalPlayer {
|
|||
|
||||
/// Update the [`LocalPlayerInLoadedChunk`] component for all [`LocalPlayer`]s.
|
||||
pub fn update_in_loaded_chunk(
|
||||
mut commands: bevy_ecs::system::Commands,
|
||||
mut commands: azalea_ecs::system::Commands,
|
||||
query: Query<(Entity, &LocalPlayer, &entity::Position)>,
|
||||
) {
|
||||
for (entity, local_player, position) in &query {
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
use std::{collections::HashSet, io::Cursor, sync::Arc};
|
||||
|
||||
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
|
||||
use azalea_ecs::{component::Component, ecs::Ecs};
|
||||
use azalea_ecs::{
|
||||
component::Component,
|
||||
prelude::{Entity, EventWriter},
|
||||
query::Changed,
|
||||
schedule::{IntoSystemDescriptor, SystemSet},
|
||||
system::{Commands, Query, ResMut, SystemState},
|
||||
App, Plugin,
|
||||
};
|
||||
use azalea_protocol::{
|
||||
connect::{ReadConnection, WriteConnection},
|
||||
packets::game::{
|
||||
|
@ -20,14 +29,6 @@ use azalea_world::{
|
|||
},
|
||||
LoadedBy, PartialWorld, RelativeEntityUpdate, WorldContainer,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
prelude::{Entity, EventWriter},
|
||||
query::Changed,
|
||||
schedule::{IntoSystemDescriptor, SystemSet},
|
||||
system::{Commands, Query, ResMut, SystemState},
|
||||
};
|
||||
use log::{debug, error, trace, warn};
|
||||
use parking_lot::Mutex;
|
||||
use tokio::sync::mpsc;
|
||||
|
@ -97,7 +98,7 @@ pub struct PacketReceiver {
|
|||
pub run_schedule_sender: mpsc::Sender<()>,
|
||||
}
|
||||
|
||||
fn handle_packets(ecs: &mut bevy_ecs::world::World) {
|
||||
fn handle_packets(ecs: &mut Ecs) {
|
||||
let mut events_owned = Vec::new();
|
||||
|
||||
{
|
||||
|
|
14
azalea-ecs/Cargo.toml
Normal file
14
azalea-ecs/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
name = "azalea-ecs"
|
||||
version = "0.5.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
azalea-ecs-macros = {path = "./azalea-ecs-macros", version = "^0.5.0"}
|
||||
bevy_app = "0.9.1"
|
||||
bevy_ecs = {version = "0.9.1", default-features = false}
|
||||
futures = "0.3.25"
|
||||
iyes_loopless = "0.9.1"
|
||||
tokio = {version = "1.25.0", features = ["time"]}
|
15
azalea-ecs/azalea-ecs-macros/Cargo.toml
Executable file
15
azalea-ecs/azalea-ecs-macros/Cargo.toml
Executable file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
description = "Azalea ECS Macros"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "azalea-ecs-macros"
|
||||
version = "0.5.0"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = "1.0"
|
||||
toml = "0.7.0"
|
126
azalea-ecs/azalea-ecs-macros/src/component.rs
Normal file
126
azalea-ecs/azalea-ecs-macros/src/component.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
use crate::utils::{get_lit_str, Symbol};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result};
|
||||
|
||||
use crate::utils;
|
||||
|
||||
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||
let azalea_ecs_path: Path = crate::azalea_ecs_path();
|
||||
|
||||
ast.generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote! { Self: Send + Sync + 'static });
|
||||
|
||||
let struct_name = &ast.ident;
|
||||
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
|
||||
|
||||
TokenStream::from(quote! {
|
||||
impl #impl_generics #azalea_ecs_path::system::Resource for #struct_name #type_generics #where_clause {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn derive_component(input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||
let azalea_ecs_path: Path = crate::azalea_ecs_path();
|
||||
|
||||
let attrs = match parse_component_attr(&ast) {
|
||||
Ok(attrs) => attrs,
|
||||
Err(e) => return e.into_compile_error().into(),
|
||||
};
|
||||
|
||||
let storage = storage_path(&azalea_ecs_path, attrs.storage);
|
||||
|
||||
ast.generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote! { Self: Send + Sync + 'static });
|
||||
|
||||
let struct_name = &ast.ident;
|
||||
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
|
||||
|
||||
TokenStream::from(quote! {
|
||||
impl #impl_generics #azalea_ecs_path::component::Component for #struct_name #type_generics #where_clause {
|
||||
type Storage = #storage;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub const COMPONENT: Symbol = Symbol("component");
|
||||
pub const STORAGE: Symbol = Symbol("storage");
|
||||
|
||||
struct Attrs {
|
||||
storage: StorageTy,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum StorageTy {
|
||||
Table,
|
||||
SparseSet,
|
||||
}
|
||||
|
||||
// values for `storage` attribute
|
||||
const TABLE: &str = "Table";
|
||||
const SPARSE_SET: &str = "SparseSet";
|
||||
|
||||
fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
||||
let meta_items = utils::parse_attrs(ast, COMPONENT)?;
|
||||
|
||||
let mut attrs = Attrs {
|
||||
storage: StorageTy::Table,
|
||||
};
|
||||
|
||||
for meta in meta_items {
|
||||
use syn::{
|
||||
Meta::NameValue,
|
||||
NestedMeta::{Lit, Meta},
|
||||
};
|
||||
match meta {
|
||||
Meta(NameValue(m)) if m.path == STORAGE => {
|
||||
attrs.storage = match get_lit_str(STORAGE, &m.lit)?.value().as_str() {
|
||||
TABLE => StorageTy::Table,
|
||||
SPARSE_SET => StorageTy::SparseSet,
|
||||
s => {
|
||||
return Err(Error::new_spanned(
|
||||
m.lit,
|
||||
format!(
|
||||
"Invalid storage type `{}`, expected '{}' or '{}'.",
|
||||
s, TABLE, SPARSE_SET
|
||||
),
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
Meta(meta_item) => {
|
||||
return Err(Error::new_spanned(
|
||||
meta_item.path(),
|
||||
format!(
|
||||
"unknown component attribute `{}`",
|
||||
meta_item.path().into_token_stream()
|
||||
),
|
||||
));
|
||||
}
|
||||
Lit(lit) => {
|
||||
return Err(Error::new_spanned(
|
||||
lit,
|
||||
"unexpected literal in component attribute",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(attrs)
|
||||
}
|
||||
|
||||
fn storage_path(azalea_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
|
||||
let typename = match ty {
|
||||
StorageTy::Table => Ident::new("TableStorage", Span::call_site()),
|
||||
StorageTy::SparseSet => Ident::new("SparseStorage", Span::call_site()),
|
||||
};
|
||||
|
||||
quote! { #azalea_ecs_path::component::#typename }
|
||||
}
|
468
azalea-ecs/azalea-ecs-macros/src/fetch.rs
Normal file
468
azalea-ecs/azalea-ecs-macros/src/fetch.rs
Normal file
|
@ -0,0 +1,468 @@
|
|||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
parse_quote,
|
||||
punctuated::Punctuated,
|
||||
Attribute, Data, DataStruct, DeriveInput, Field, Fields,
|
||||
};
|
||||
|
||||
use crate::azalea_ecs_path;
|
||||
|
||||
#[derive(Default)]
|
||||
struct FetchStructAttributes {
|
||||
pub is_mutable: bool,
|
||||
pub derive_args: Punctuated<syn::NestedMeta, syn::token::Comma>,
|
||||
}
|
||||
|
||||
static MUTABLE_ATTRIBUTE_NAME: &str = "mutable";
|
||||
static DERIVE_ATTRIBUTE_NAME: &str = "derive";
|
||||
|
||||
mod field_attr_keywords {
|
||||
syn::custom_keyword!(ignore);
|
||||
}
|
||||
|
||||
pub static WORLD_QUERY_ATTRIBUTE_NAME: &str = "world_query";
|
||||
|
||||
pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
|
||||
let visibility = ast.vis;
|
||||
|
||||
let mut fetch_struct_attributes = FetchStructAttributes::default();
|
||||
for attr in &ast.attrs {
|
||||
if !attr
|
||||
.path
|
||||
.get_ident()
|
||||
.map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
attr.parse_args_with(|input: ParseStream| {
|
||||
let meta = input.parse_terminated::<syn::Meta, syn::token::Comma>(syn::Meta::parse)?;
|
||||
for meta in meta {
|
||||
let ident = meta.path().get_ident().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Unrecognized attribute: `{}`",
|
||||
meta.path().to_token_stream()
|
||||
)
|
||||
});
|
||||
if ident == MUTABLE_ATTRIBUTE_NAME {
|
||||
if let syn::Meta::Path(_) = meta {
|
||||
fetch_struct_attributes.is_mutable = true;
|
||||
} else {
|
||||
panic!(
|
||||
"The `{}` attribute is expected to have no value or arguments",
|
||||
MUTABLE_ATTRIBUTE_NAME
|
||||
);
|
||||
}
|
||||
} else if ident == DERIVE_ATTRIBUTE_NAME {
|
||||
if let syn::Meta::List(meta_list) = meta {
|
||||
fetch_struct_attributes
|
||||
.derive_args
|
||||
.extend(meta_list.nested.iter().cloned());
|
||||
} else {
|
||||
panic!(
|
||||
"Expected a structured list within the `{}` attribute",
|
||||
DERIVE_ATTRIBUTE_NAME
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"Unrecognized attribute: `{}`",
|
||||
meta.path().to_token_stream()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap_or_else(|_| panic!("Invalid `{WORLD_QUERY_ATTRIBUTE_NAME}` attribute format"));
|
||||
}
|
||||
|
||||
let path = azalea_ecs_path();
|
||||
|
||||
let user_generics = ast.generics.clone();
|
||||
let (user_impl_generics, user_ty_generics, user_where_clauses) = user_generics.split_for_impl();
|
||||
let user_generics_with_world = {
|
||||
let mut generics = ast.generics.clone();
|
||||
generics.params.insert(0, parse_quote!('__w));
|
||||
generics
|
||||
};
|
||||
let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) =
|
||||
user_generics_with_world.split_for_impl();
|
||||
|
||||
let struct_name = ast.ident.clone();
|
||||
let read_only_struct_name = if fetch_struct_attributes.is_mutable {
|
||||
Ident::new(&format!("{struct_name}ReadOnly"), Span::call_site())
|
||||
} else {
|
||||
struct_name.clone()
|
||||
};
|
||||
|
||||
let item_struct_name = Ident::new(&format!("{struct_name}Item"), Span::call_site());
|
||||
let read_only_item_struct_name = if fetch_struct_attributes.is_mutable {
|
||||
Ident::new(&format!("{struct_name}ReadOnlyItem"), Span::call_site())
|
||||
} else {
|
||||
item_struct_name.clone()
|
||||
};
|
||||
|
||||
let fetch_struct_name = Ident::new(&format!("{struct_name}Fetch"), Span::call_site());
|
||||
let read_only_fetch_struct_name = if fetch_struct_attributes.is_mutable {
|
||||
Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site())
|
||||
} else {
|
||||
fetch_struct_name.clone()
|
||||
};
|
||||
|
||||
let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site());
|
||||
|
||||
let fields = match &ast.data {
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Named(fields),
|
||||
..
|
||||
}) => &fields.named,
|
||||
_ => panic!("Expected a struct with named fields"),
|
||||
};
|
||||
|
||||
let mut ignored_field_attrs = Vec::new();
|
||||
let mut ignored_field_visibilities = Vec::new();
|
||||
let mut ignored_field_idents = Vec::new();
|
||||
let mut ignored_field_types = Vec::new();
|
||||
let mut field_attrs = Vec::new();
|
||||
let mut field_visibilities = Vec::new();
|
||||
let mut field_idents = Vec::new();
|
||||
let mut field_types = Vec::new();
|
||||
let mut read_only_field_types = Vec::new();
|
||||
|
||||
for field in fields {
|
||||
let WorldQueryFieldInfo { is_ignored, attrs } = read_world_query_field_info(field);
|
||||
|
||||
let field_ident = field.ident.as_ref().unwrap().clone();
|
||||
if is_ignored {
|
||||
ignored_field_attrs.push(attrs);
|
||||
ignored_field_visibilities.push(field.vis.clone());
|
||||
ignored_field_idents.push(field_ident.clone());
|
||||
ignored_field_types.push(field.ty.clone());
|
||||
} else {
|
||||
field_attrs.push(attrs);
|
||||
field_visibilities.push(field.vis.clone());
|
||||
field_idents.push(field_ident.clone());
|
||||
let field_ty = field.ty.clone();
|
||||
field_types.push(quote!(#field_ty));
|
||||
read_only_field_types.push(quote!(<#field_ty as #path::query::WorldQuery>::ReadOnly));
|
||||
}
|
||||
}
|
||||
|
||||
let derive_args = &fetch_struct_attributes.derive_args;
|
||||
// `#[derive()]` is valid syntax
|
||||
let derive_macro_call = quote! { #[derive(#derive_args)] };
|
||||
|
||||
let impl_fetch = |is_readonly: bool| {
|
||||
let struct_name = if is_readonly {
|
||||
&read_only_struct_name
|
||||
} else {
|
||||
&struct_name
|
||||
};
|
||||
let item_struct_name = if is_readonly {
|
||||
&read_only_item_struct_name
|
||||
} else {
|
||||
&item_struct_name
|
||||
};
|
||||
let fetch_struct_name = if is_readonly {
|
||||
&read_only_fetch_struct_name
|
||||
} else {
|
||||
&fetch_struct_name
|
||||
};
|
||||
|
||||
let field_types = if is_readonly {
|
||||
&read_only_field_types
|
||||
} else {
|
||||
&field_types
|
||||
};
|
||||
|
||||
quote! {
|
||||
#derive_macro_call
|
||||
#[doc = "Automatically generated [`WorldQuery`] item type for [`"]
|
||||
#[doc = stringify!(#struct_name)]
|
||||
#[doc = "`], returned when iterating over query results."]
|
||||
#[automatically_derived]
|
||||
#visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world {
|
||||
#(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)*
|
||||
#(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)*
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[doc = "Automatically generated internal [`WorldQuery`] fetch type for [`"]
|
||||
#[doc = stringify!(#struct_name)]
|
||||
#[doc = "`], used to define the world data accessed by this query."]
|
||||
#[automatically_derived]
|
||||
#visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world {
|
||||
#(#field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)*
|
||||
#(#ignored_field_idents: #ignored_field_types,)*
|
||||
}
|
||||
|
||||
// SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field
|
||||
unsafe impl #user_impl_generics #path::query::WorldQuery
|
||||
for #struct_name #user_ty_generics #user_where_clauses {
|
||||
|
||||
type Item<'__w> = #item_struct_name #user_ty_generics_with_world;
|
||||
type Fetch<'__w> = #fetch_struct_name #user_ty_generics_with_world;
|
||||
type ReadOnly = #read_only_struct_name #user_ty_generics;
|
||||
type State = #state_struct_name #user_ty_generics;
|
||||
|
||||
fn shrink<'__wlong: '__wshort, '__wshort>(
|
||||
item: <#struct_name #user_ty_generics as #path::query::WorldQuery>::Item<'__wlong>
|
||||
) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Item<'__wshort> {
|
||||
#item_struct_name {
|
||||
#(
|
||||
#field_idents: <#field_types>::shrink(item.#field_idents),
|
||||
)*
|
||||
#(
|
||||
#ignored_field_idents: item.#ignored_field_idents,
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init_fetch<'__w>(
|
||||
_world: &'__w #path::world::World,
|
||||
state: &Self::State,
|
||||
_last_change_tick: u32,
|
||||
_change_tick: u32
|
||||
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
|
||||
#fetch_struct_name {
|
||||
#(#field_idents:
|
||||
<#field_types>::init_fetch(
|
||||
_world,
|
||||
&state.#field_idents,
|
||||
_last_change_tick,
|
||||
_change_tick
|
||||
),
|
||||
)*
|
||||
#(#ignored_field_idents: Default::default(),)*
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn clone_fetch<'__w>(
|
||||
_fetch: &<Self as #path::query::WorldQuery>::Fetch<'__w>
|
||||
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
|
||||
#fetch_struct_name {
|
||||
#(
|
||||
#field_idents: <#field_types>::clone_fetch(& _fetch. #field_idents),
|
||||
)*
|
||||
#(
|
||||
#ignored_field_idents: Default::default(),
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
const IS_DENSE: bool = true #(&& <#field_types>::IS_DENSE)*;
|
||||
|
||||
const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*;
|
||||
|
||||
/// SAFETY: we call `set_archetype` for each member that implements `Fetch`
|
||||
#[inline]
|
||||
unsafe fn set_archetype<'__w>(
|
||||
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
|
||||
_state: &Self::State,
|
||||
_archetype: &'__w #path::archetype::Archetype,
|
||||
_table: &'__w #path::storage::Table
|
||||
) {
|
||||
#(<#field_types>::set_archetype(&mut _fetch.#field_idents, &_state.#field_idents, _archetype, _table);)*
|
||||
}
|
||||
|
||||
/// SAFETY: we call `set_table` for each member that implements `Fetch`
|
||||
#[inline]
|
||||
unsafe fn set_table<'__w>(
|
||||
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
|
||||
_state: &Self::State,
|
||||
_table: &'__w #path::storage::Table
|
||||
) {
|
||||
#(<#field_types>::set_table(&mut _fetch.#field_idents, &_state.#field_idents, _table);)*
|
||||
}
|
||||
|
||||
/// SAFETY: we call `fetch` for each member that implements `Fetch`.
|
||||
#[inline(always)]
|
||||
unsafe fn fetch<'__w>(
|
||||
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
|
||||
_entity: Entity,
|
||||
_table_row: usize
|
||||
) -> <Self as #path::query::WorldQuery>::Item<'__w> {
|
||||
Self::Item {
|
||||
#(#field_idents: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)*
|
||||
#(#ignored_field_idents: Default::default(),)*
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
unsafe fn filter_fetch<'__w>(
|
||||
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
|
||||
_entity: Entity,
|
||||
_table_row: usize
|
||||
) -> bool {
|
||||
true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))*
|
||||
}
|
||||
|
||||
fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) {
|
||||
#( <#field_types>::update_component_access(&state.#field_idents, _access); )*
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(
|
||||
state: &Self::State,
|
||||
_archetype: &#path::archetype::Archetype,
|
||||
_access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId>
|
||||
) {
|
||||
#(
|
||||
<#field_types>::update_archetype_component_access(&state.#field_idents, _archetype, _access);
|
||||
)*
|
||||
}
|
||||
|
||||
fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics {
|
||||
#state_struct_name {
|
||||
#(#field_idents: <#field_types>::init_state(world),)*
|
||||
#(#ignored_field_idents: Default::default(),)*
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool {
|
||||
true #(&& <#field_types>::matches_component_set(&state.#field_idents, _set_contains_id))*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mutable_impl = impl_fetch(false);
|
||||
let readonly_impl = if fetch_struct_attributes.is_mutable {
|
||||
let world_query_impl = impl_fetch(true);
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
#[doc = "Automatically generated internal [`WorldQuery`] type for [`"]
|
||||
#[doc = stringify!(#struct_name)]
|
||||
#[doc = "`], used for read-only access."]
|
||||
#[automatically_derived]
|
||||
#visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses {
|
||||
#( #field_idents: #read_only_field_types, )*
|
||||
#(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)*
|
||||
}
|
||||
|
||||
#world_query_impl
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let read_only_asserts = if fetch_struct_attributes.is_mutable {
|
||||
quote! {
|
||||
// Double-check that the data fetched by `<_ as WorldQuery>::ReadOnly` is read-only.
|
||||
// This is technically unnecessary as `<_ as WorldQuery>::ReadOnly: ReadOnlyWorldQuery`
|
||||
// but to protect against future mistakes we assert the assoc type implements `ReadOnlyWorldQuery` anyway
|
||||
#( assert_readonly::<#read_only_field_types>(); )*
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
// Statically checks that the safety guarantee of `ReadOnlyWorldQuery` for `$fetch_struct_name` actually holds true.
|
||||
// We need this to make sure that we don't compile `ReadOnlyWorldQuery` if our struct contains nested `WorldQuery`
|
||||
// members that don't implement it. I.e.:
|
||||
// ```
|
||||
// #[derive(WorldQuery)]
|
||||
// pub struct Foo { a: &'static mut MyComponent }
|
||||
// ```
|
||||
#( assert_readonly::<#field_types>(); )*
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(quote! {
|
||||
#mutable_impl
|
||||
|
||||
#readonly_impl
|
||||
|
||||
#[doc(hidden)]
|
||||
#[doc = "Automatically generated internal [`WorldQuery`] state type for [`"]
|
||||
#[doc = stringify!(#struct_name)]
|
||||
#[doc = "`], used for caching."]
|
||||
#[automatically_derived]
|
||||
#visibility struct #state_struct_name #user_impl_generics #user_where_clauses {
|
||||
#(#field_idents: <#field_types as #path::query::WorldQuery>::State,)*
|
||||
#(#ignored_field_idents: #ignored_field_types,)*
|
||||
}
|
||||
|
||||
/// SAFETY: we assert fields are readonly below
|
||||
unsafe impl #user_impl_generics #path::query::ReadOnlyWorldQuery
|
||||
for #read_only_struct_name #user_ty_generics #user_where_clauses {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const _: () = {
|
||||
fn assert_readonly<T>()
|
||||
where
|
||||
T: #path::query::ReadOnlyWorldQuery,
|
||||
{
|
||||
}
|
||||
|
||||
// We generate a readonly assertion for every struct member.
|
||||
fn assert_all #user_impl_generics_with_world () #user_where_clauses_with_world {
|
||||
#read_only_asserts
|
||||
}
|
||||
};
|
||||
|
||||
// The original struct will most likely be left unused. As we don't want our users having
|
||||
// to specify `#[allow(dead_code)]` for their custom queries, we are using this cursed
|
||||
// workaround.
|
||||
#[allow(dead_code)]
|
||||
const _: () = {
|
||||
fn dead_code_workaround #user_impl_generics (
|
||||
q: #struct_name #user_ty_generics,
|
||||
q2: #read_only_struct_name #user_ty_generics
|
||||
) #user_where_clauses {
|
||||
#(q.#field_idents;)*
|
||||
#(q.#ignored_field_idents;)*
|
||||
#(q2.#field_idents;)*
|
||||
#(q2.#ignored_field_idents;)*
|
||||
|
||||
}
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
struct WorldQueryFieldInfo {
|
||||
/// Has `#[fetch(ignore)]` or `#[filter_fetch(ignore)]` attribute.
|
||||
is_ignored: bool,
|
||||
/// All field attributes except for `world_query` ones.
|
||||
attrs: Vec<Attribute>,
|
||||
}
|
||||
|
||||
fn read_world_query_field_info(field: &Field) -> WorldQueryFieldInfo {
|
||||
let is_ignored = field
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|attr| {
|
||||
attr.path
|
||||
.get_ident()
|
||||
.map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME)
|
||||
})
|
||||
.map_or(false, |attr| {
|
||||
let mut is_ignored = false;
|
||||
attr.parse_args_with(|input: ParseStream| {
|
||||
if input
|
||||
.parse::<Option<field_attr_keywords::ignore>>()?
|
||||
.is_some()
|
||||
{
|
||||
is_ignored = true;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap_or_else(|_| panic!("Invalid `{WORLD_QUERY_ATTRIBUTE_NAME}` attribute format"));
|
||||
|
||||
is_ignored
|
||||
});
|
||||
|
||||
let attrs = field
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| {
|
||||
attr.path
|
||||
.get_ident()
|
||||
.map_or(true, |ident| ident != WORLD_QUERY_ATTRIBUTE_NAME)
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
WorldQueryFieldInfo { is_ignored, attrs }
|
||||
}
|
523
azalea-ecs/azalea-ecs-macros/src/lib.rs
Executable file
523
azalea-ecs/azalea-ecs-macros/src/lib.rs
Executable file
|
@ -0,0 +1,523 @@
|
|||
//! A fork of bevy_ecs_macros that uses azalea_ecs instead of bevy_ecs.
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
mod component;
|
||||
mod fetch;
|
||||
pub(crate) mod utils;
|
||||
|
||||
use crate::fetch::derive_world_query_impl;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token::Comma,
|
||||
DeriveInput, Field, GenericParam, Ident, Index, LitInt, Meta, MetaList, NestedMeta, Result,
|
||||
Token, TypeParam,
|
||||
};
|
||||
use utils::{derive_label, get_named_struct_fields, BevyManifest};
|
||||
|
||||
struct AllTuples {
|
||||
macro_ident: Ident,
|
||||
start: usize,
|
||||
end: usize,
|
||||
idents: Vec<Ident>,
|
||||
}
|
||||
|
||||
impl Parse for AllTuples {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let macro_ident = input.parse::<Ident>()?;
|
||||
input.parse::<Comma>()?;
|
||||
let start = input.parse::<LitInt>()?.base10_parse()?;
|
||||
input.parse::<Comma>()?;
|
||||
let end = input.parse::<LitInt>()?.base10_parse()?;
|
||||
input.parse::<Comma>()?;
|
||||
let mut idents = vec![input.parse::<Ident>()?];
|
||||
while input.parse::<Comma>().is_ok() {
|
||||
idents.push(input.parse::<Ident>()?);
|
||||
}
|
||||
|
||||
Ok(AllTuples {
|
||||
macro_ident,
|
||||
start,
|
||||
end,
|
||||
idents,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn all_tuples(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as AllTuples);
|
||||
let len = input.end - input.start;
|
||||
let mut ident_tuples = Vec::with_capacity(len);
|
||||
for i in input.start..=input.end {
|
||||
let idents = input
|
||||
.idents
|
||||
.iter()
|
||||
.map(|ident| format_ident!("{}{}", ident, i));
|
||||
if input.idents.len() < 2 {
|
||||
ident_tuples.push(quote! {
|
||||
#(#idents)*
|
||||
});
|
||||
} else {
|
||||
ident_tuples.push(quote! {
|
||||
(#(#idents),*)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let macro_ident = &input.macro_ident;
|
||||
let invocations = (input.start..=input.end).map(|i| {
|
||||
let ident_tuples = &ident_tuples[..i];
|
||||
quote! {
|
||||
#macro_ident!(#(#ident_tuples),*);
|
||||
}
|
||||
});
|
||||
TokenStream::from(quote! {
|
||||
#(
|
||||
#invocations
|
||||
)*
|
||||
})
|
||||
}
|
||||
|
||||
enum BundleFieldKind {
|
||||
Component,
|
||||
Ignore,
|
||||
}
|
||||
|
||||
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
|
||||
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
|
||||
|
||||
#[proc_macro_derive(Bundle, attributes(bundle))]
|
||||
pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let ecs_path = azalea_ecs_path();
|
||||
|
||||
let named_fields = match get_named_struct_fields(&ast.data) {
|
||||
Ok(fields) => &fields.named,
|
||||
Err(e) => return e.into_compile_error().into(),
|
||||
};
|
||||
|
||||
let mut field_kind = Vec::with_capacity(named_fields.len());
|
||||
|
||||
'field_loop: for field in named_fields.iter() {
|
||||
for attr in &field.attrs {
|
||||
if attr.path.is_ident(BUNDLE_ATTRIBUTE_NAME) {
|
||||
if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
|
||||
if let Some(&NestedMeta::Meta(Meta::Path(ref path))) = nested.first() {
|
||||
if path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
|
||||
field_kind.push(BundleFieldKind::Ignore);
|
||||
continue 'field_loop;
|
||||
}
|
||||
|
||||
return syn::Error::new(
|
||||
path.span(),
|
||||
format!(
|
||||
"Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`"
|
||||
),
|
||||
)
|
||||
.into_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
return syn::Error::new(attr.span(), format!("Invalid bundle attribute. Use `#[{BUNDLE_ATTRIBUTE_NAME}({BUNDLE_ATTRIBUTE_IGNORE_NAME})]`")).into_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
field_kind.push(BundleFieldKind::Component);
|
||||
}
|
||||
|
||||
let field = named_fields
|
||||
.iter()
|
||||
.map(|field| field.ident.as_ref().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let field_type = named_fields
|
||||
.iter()
|
||||
.map(|field| &field.ty)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut field_component_ids = Vec::new();
|
||||
let mut field_get_components = Vec::new();
|
||||
let mut field_from_components = Vec::new();
|
||||
for ((field_type, field_kind), field) in
|
||||
field_type.iter().zip(field_kind.iter()).zip(field.iter())
|
||||
{
|
||||
match field_kind {
|
||||
BundleFieldKind::Component => {
|
||||
field_component_ids.push(quote! {
|
||||
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids);
|
||||
});
|
||||
field_get_components.push(quote! {
|
||||
self.#field.get_components(&mut *func);
|
||||
});
|
||||
field_from_components.push(quote! {
|
||||
#field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func),
|
||||
});
|
||||
}
|
||||
|
||||
BundleFieldKind::Ignore => {
|
||||
field_from_components.push(quote! {
|
||||
#field: ::std::default::Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let generics = ast.generics;
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let struct_name = &ast.ident;
|
||||
|
||||
TokenStream::from(quote! {
|
||||
/// SAFETY: ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order
|
||||
unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause {
|
||||
fn component_ids(
|
||||
components: &mut #ecs_path::component::Components,
|
||||
storages: &mut #ecs_path::storage::Storages,
|
||||
ids: &mut impl FnMut(#ecs_path::component::ComponentId)
|
||||
){
|
||||
#(#field_component_ids)*
|
||||
}
|
||||
|
||||
#[allow(unused_variables, non_snake_case)]
|
||||
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
|
||||
where
|
||||
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
|
||||
{
|
||||
Self {
|
||||
#(#field_from_components)*
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn get_components(self, func: &mut impl FnMut(#ecs_path::ptr::OwningPtr<'_>)) {
|
||||
#(#field_get_components)*
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_idents(fmt_string: fn(usize) -> String, count: usize) -> Vec<Ident> {
|
||||
(0..count)
|
||||
.map(|i| Ident::new(&fmt_string(i), Span::call_site()))
|
||||
.collect::<Vec<Ident>>()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn impl_param_set(_input: TokenStream) -> TokenStream {
|
||||
let mut tokens = TokenStream::new();
|
||||
let max_params = 8;
|
||||
let params = get_idents(|i| format!("P{i}"), max_params);
|
||||
let params_fetch = get_idents(|i| format!("PF{i}"), max_params);
|
||||
let metas = get_idents(|i| format!("m{i}"), max_params);
|
||||
let mut param_fn_muts = Vec::new();
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let fn_name = Ident::new(&format!("p{i}"), Span::call_site());
|
||||
let index = Index::from(i);
|
||||
param_fn_muts.push(quote! {
|
||||
pub fn #fn_name<'a>(&'a mut self) -> <#param::Fetch as SystemParamFetch<'a, 'a>>::Item {
|
||||
// SAFETY: systems run without conflicts with other systems.
|
||||
// Conflicting params in ParamSet are not accessible at the same time
|
||||
// ParamSets are guaranteed to not conflict with other SystemParams
|
||||
unsafe {
|
||||
<#param::Fetch as SystemParamFetch<'a, 'a>>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for param_count in 1..=max_params {
|
||||
let param = ¶ms[0..param_count];
|
||||
let param_fetch = ¶ms_fetch[0..param_count];
|
||||
let meta = &metas[0..param_count];
|
||||
let param_fn_mut = ¶m_fn_muts[0..param_count];
|
||||
tokens.extend(TokenStream::from(quote! {
|
||||
impl<'w, 's, #(#param: SystemParam,)*> SystemParam for ParamSet<'w, 's, (#(#param,)*)>
|
||||
{
|
||||
type Fetch = ParamSetState<(#(#param::Fetch,)*)>;
|
||||
}
|
||||
|
||||
// SAFETY: All parameters are constrained to ReadOnlyFetch, so World is only read
|
||||
|
||||
unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> ReadOnlySystemParamFetch for ParamSetState<(#(#param_fetch,)*)>
|
||||
where #(#param_fetch: ReadOnlySystemParamFetch,)*
|
||||
{ }
|
||||
|
||||
// SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts
|
||||
// with any prior access, a panic will occur.
|
||||
|
||||
unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamState for ParamSetState<(#(#param_fetch,)*)>
|
||||
{
|
||||
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
|
||||
#(
|
||||
// Pretend to add each param to the system alone, see if it conflicts
|
||||
let mut #meta = system_meta.clone();
|
||||
#meta.component_access_set.clear();
|
||||
#meta.archetype_component_access.clear();
|
||||
#param_fetch::init(world, &mut #meta);
|
||||
let #param = #param_fetch::init(world, &mut system_meta.clone());
|
||||
)*
|
||||
#(
|
||||
system_meta
|
||||
.component_access_set
|
||||
.extend(#meta.component_access_set);
|
||||
system_meta
|
||||
.archetype_component_access
|
||||
.extend(&#meta.archetype_component_access);
|
||||
)*
|
||||
ParamSetState((#(#param,)*))
|
||||
}
|
||||
|
||||
fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) {
|
||||
let (#(#param,)*) = &mut self.0;
|
||||
#(
|
||||
#param.new_archetype(archetype, system_meta);
|
||||
)*
|
||||
}
|
||||
|
||||
fn apply(&mut self, world: &mut World) {
|
||||
self.0.apply(world)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl<'w, 's, #(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamFetch<'w, 's> for ParamSetState<(#(#param_fetch,)*)>
|
||||
{
|
||||
type Item = ParamSet<'w, 's, (#(<#param_fetch as SystemParamFetch<'w, 's>>::Item,)*)>;
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param(
|
||||
state: &'s mut Self,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
) -> Self::Item {
|
||||
ParamSet {
|
||||
param_states: &mut state.0,
|
||||
system_meta: system_meta.clone(),
|
||||
world,
|
||||
change_tick,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, 's, #(#param: SystemParam,)*> ParamSet<'w, 's, (#(#param,)*)>
|
||||
{
|
||||
|
||||
#(#param_fn_mut)*
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SystemParamFieldAttributes {
|
||||
pub ignore: bool,
|
||||
}
|
||||
|
||||
static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param";
|
||||
|
||||
/// Implement `SystemParam` to use a struct as a parameter in a system
|
||||
#[proc_macro_derive(SystemParam, attributes(system_param))]
|
||||
pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let fields = match get_named_struct_fields(&ast.data) {
|
||||
Ok(fields) => &fields.named,
|
||||
Err(e) => return e.into_compile_error().into(),
|
||||
};
|
||||
let path = azalea_ecs_path();
|
||||
|
||||
let field_attributes = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
(
|
||||
field,
|
||||
field
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|a| *a.path.get_ident().as_ref().unwrap() == SYSTEM_PARAM_ATTRIBUTE_NAME)
|
||||
.map_or_else(SystemParamFieldAttributes::default, |a| {
|
||||
syn::custom_keyword!(ignore);
|
||||
let mut attributes = SystemParamFieldAttributes::default();
|
||||
a.parse_args_with(|input: ParseStream| {
|
||||
if input.parse::<Option<ignore>>()?.is_some() {
|
||||
attributes.ignore = true;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.expect("Invalid 'system_param' attribute format.");
|
||||
|
||||
attributes
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(&Field, SystemParamFieldAttributes)>>();
|
||||
let mut fields = Vec::new();
|
||||
let mut field_indices = Vec::new();
|
||||
let mut field_types = Vec::new();
|
||||
let mut ignored_fields = Vec::new();
|
||||
let mut ignored_field_types = Vec::new();
|
||||
for (i, (field, attrs)) in field_attributes.iter().enumerate() {
|
||||
if attrs.ignore {
|
||||
ignored_fields.push(field.ident.as_ref().unwrap());
|
||||
ignored_field_types.push(&field.ty);
|
||||
} else {
|
||||
fields.push(field.ident.as_ref().unwrap());
|
||||
field_types.push(&field.ty);
|
||||
field_indices.push(Index::from(i));
|
||||
}
|
||||
}
|
||||
|
||||
let generics = ast.generics;
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let lifetimeless_generics: Vec<_> = generics
|
||||
.params
|
||||
.iter()
|
||||
.filter(|g| matches!(g, GenericParam::Type(_)))
|
||||
.collect();
|
||||
|
||||
let mut punctuated_generics = Punctuated::<_, Token![,]>::new();
|
||||
punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g {
|
||||
GenericParam::Type(g) => GenericParam::Type(TypeParam {
|
||||
default: None,
|
||||
..g.clone()
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
}));
|
||||
|
||||
let mut punctuated_generic_idents = Punctuated::<_, Token![,]>::new();
|
||||
punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g {
|
||||
GenericParam::Type(g) => &g.ident,
|
||||
_ => unreachable!(),
|
||||
}));
|
||||
|
||||
let struct_name = &ast.ident;
|
||||
let fetch_struct_visibility = &ast.vis;
|
||||
|
||||
TokenStream::from(quote! {
|
||||
// We define the FetchState struct in an anonymous scope to avoid polluting the user namespace.
|
||||
// The struct can still be accessed via SystemParam::Fetch, e.g. EventReaderState can be accessed via
|
||||
// <EventReader<'static, 'static, T> as SystemParam>::Fetch
|
||||
const _: () = {
|
||||
impl #impl_generics #path::system::SystemParam for #struct_name #ty_generics #where_clause {
|
||||
type Fetch = FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents>;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#fetch_struct_visibility struct FetchState <TSystemParamState, #punctuated_generic_idents> {
|
||||
state: TSystemParamState,
|
||||
marker: std::marker::PhantomData<fn()->(#punctuated_generic_idents)>
|
||||
}
|
||||
|
||||
unsafe impl<TSystemParamState: #path::system::SystemParamState, #punctuated_generics> #path::system::SystemParamState for FetchState <TSystemParamState, #punctuated_generic_idents> #where_clause {
|
||||
fn init(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self {
|
||||
Self {
|
||||
state: TSystemParamState::init(world, system_meta),
|
||||
marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_archetype(&mut self, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) {
|
||||
self.state.new_archetype(archetype, system_meta)
|
||||
}
|
||||
|
||||
fn apply(&mut self, world: &mut #path::world::World) {
|
||||
self.state.apply(world)
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #path::system::SystemParamFetch<'w, 's> for FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents> #where_clause {
|
||||
type Item = #struct_name #ty_generics;
|
||||
unsafe fn get_param(
|
||||
state: &'s mut Self,
|
||||
system_meta: &#path::system::SystemMeta,
|
||||
world: &'w #path::world::World,
|
||||
change_tick: u32,
|
||||
) -> Self::Item {
|
||||
#struct_name {
|
||||
#(#fields: <<#field_types as #path::system::SystemParam>::Fetch as #path::system::SystemParamFetch>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick),)*
|
||||
#(#ignored_fields: <#ignored_field_types>::default(),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: The `ParamState` is `ReadOnlySystemParamFetch`, so this can only read from the `World`
|
||||
unsafe impl<TSystemParamState: #path::system::SystemParamState + #path::system::ReadOnlySystemParamFetch, #punctuated_generics> #path::system::ReadOnlySystemParamFetch for FetchState <TSystemParamState, #punctuated_generic_idents> #where_clause {}
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
/// Implement `WorldQuery` to use a struct as a parameter in a query
|
||||
#[proc_macro_derive(WorldQuery, attributes(world_query))]
|
||||
pub fn derive_world_query(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
derive_world_query_impl(ast)
|
||||
}
|
||||
|
||||
/// Generates an impl of the `SystemLabel` trait.
|
||||
///
|
||||
/// This works only for unit structs, or enums with only unit variants.
|
||||
/// You may force a struct or variant to behave as if it were fieldless with
|
||||
/// `#[system_label(ignore_fields)]`.
|
||||
#[proc_macro_derive(SystemLabel, attributes(system_label))]
|
||||
pub fn derive_system_label(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let mut trait_path = azalea_ecs_path();
|
||||
trait_path.segments.push(format_ident!("schedule").into());
|
||||
trait_path
|
||||
.segments
|
||||
.push(format_ident!("SystemLabel").into());
|
||||
derive_label(input, &trait_path, "system_label")
|
||||
}
|
||||
|
||||
/// Generates an impl of the `StageLabel` trait.
|
||||
///
|
||||
/// This works only for unit structs, or enums with only unit variants.
|
||||
/// You may force a struct or variant to behave as if it were fieldless with
|
||||
/// `#[stage_label(ignore_fields)]`.
|
||||
#[proc_macro_derive(StageLabel, attributes(stage_label))]
|
||||
pub fn derive_stage_label(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let mut trait_path = azalea_ecs_path();
|
||||
trait_path.segments.push(format_ident!("schedule").into());
|
||||
trait_path.segments.push(format_ident!("StageLabel").into());
|
||||
derive_label(input, &trait_path, "stage_label")
|
||||
}
|
||||
|
||||
/// Generates an impl of the `RunCriteriaLabel` trait.
|
||||
///
|
||||
/// This works only for unit structs, or enums with only unit variants.
|
||||
/// You may force a struct or variant to behave as if it were fieldless with
|
||||
/// `#[run_criteria_label(ignore_fields)]`.
|
||||
#[proc_macro_derive(RunCriteriaLabel, attributes(run_criteria_label))]
|
||||
pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let mut trait_path = azalea_ecs_path();
|
||||
trait_path.segments.push(format_ident!("schedule").into());
|
||||
trait_path
|
||||
.segments
|
||||
.push(format_ident!("RunCriteriaLabel").into());
|
||||
derive_label(input, &trait_path, "run_criteria_label")
|
||||
}
|
||||
|
||||
pub(crate) fn azalea_ecs_path() -> syn::Path {
|
||||
BevyManifest::default().get_path("azalea_ecs")
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Resource)]
|
||||
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
||||
component::derive_resource(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Component, attributes(component))]
|
||||
pub fn derive_component(input: TokenStream) -> TokenStream {
|
||||
component::derive_component(input)
|
||||
}
|
47
azalea-ecs/azalea-ecs-macros/src/utils/attrs.rs
Normal file
47
azalea-ecs/azalea-ecs-macros/src/utils/attrs.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use syn::DeriveInput;
|
||||
|
||||
use super::symbol::Symbol;
|
||||
|
||||
pub fn parse_attrs(ast: &DeriveInput, attr_name: Symbol) -> syn::Result<Vec<syn::NestedMeta>> {
|
||||
let mut list = Vec::new();
|
||||
for attr in ast.attrs.iter().filter(|a| a.path == attr_name) {
|
||||
match attr.parse_meta()? {
|
||||
syn::Meta::List(meta) => list.extend(meta.nested.into_iter()),
|
||||
other => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
other,
|
||||
format!("expected #[{attr_name}(...)]"),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
pub fn get_lit_str(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<&syn::LitStr> {
|
||||
if let syn::Lit::Str(lit) = lit {
|
||||
Ok(lit)
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
lit,
|
||||
format!(
|
||||
"expected {} attribute to be a string: `{} = \"...\"`",
|
||||
attr_name, attr_name
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_lit_bool(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<bool> {
|
||||
if let syn::Lit::Bool(lit) = lit {
|
||||
Ok(lit.value())
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
lit,
|
||||
format!(
|
||||
"expected {} attribute to be a bool value, `true` or `false`: `{} = ...`",
|
||||
attr_name, attr_name
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
218
azalea-ecs/azalea-ecs-macros/src/utils/mod.rs
Normal file
218
azalea-ecs/azalea-ecs-macros/src/utils/mod.rs
Normal file
|
@ -0,0 +1,218 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
mod attrs;
|
||||
mod shape;
|
||||
mod symbol;
|
||||
|
||||
pub use attrs::*;
|
||||
pub use shape::*;
|
||||
pub use symbol::*;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use std::{env, path::PathBuf};
|
||||
use syn::spanned::Spanned;
|
||||
use toml::{map::Map, Value};
|
||||
|
||||
pub struct BevyManifest {
|
||||
manifest: Map<String, Value>,
|
||||
}
|
||||
|
||||
impl Default for BevyManifest {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
manifest: env::var_os("CARGO_MANIFEST_DIR")
|
||||
.map(PathBuf::from)
|
||||
.map(|mut path| {
|
||||
path.push("Cargo.toml");
|
||||
let manifest = std::fs::read_to_string(path).unwrap();
|
||||
toml::from_str(&manifest).unwrap()
|
||||
})
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BevyManifest {
|
||||
pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> {
|
||||
const BEVY: &str = "bevy";
|
||||
const BEVY_INTERNAL: &str = "bevy_internal";
|
||||
|
||||
fn dep_package(dep: &Value) -> Option<&str> {
|
||||
if dep.as_str().is_some() {
|
||||
None
|
||||
} else {
|
||||
dep.as_table()
|
||||
.unwrap()
|
||||
.get("package")
|
||||
.map(|name| name.as_str().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
let find_in_deps = |deps: &Map<String, Value>| -> Option<syn::Path> {
|
||||
let package = if let Some(dep) = deps.get(name) {
|
||||
return Some(Self::parse_str(dep_package(dep).unwrap_or(name)));
|
||||
} else if let Some(dep) = deps.get(BEVY) {
|
||||
dep_package(dep).unwrap_or(BEVY)
|
||||
} else if let Some(dep) = deps.get(BEVY_INTERNAL) {
|
||||
dep_package(dep).unwrap_or(BEVY_INTERNAL)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut path = Self::parse_str::<syn::Path>(package);
|
||||
if let Some(module) = name.strip_prefix("bevy_") {
|
||||
path.segments.push(Self::parse_str(module));
|
||||
}
|
||||
Some(path)
|
||||
};
|
||||
|
||||
let deps = self
|
||||
.manifest
|
||||
.get("dependencies")
|
||||
.map(|deps| deps.as_table().unwrap());
|
||||
let deps_dev = self
|
||||
.manifest
|
||||
.get("dev-dependencies")
|
||||
.map(|deps| deps.as_table().unwrap());
|
||||
|
||||
deps.and_then(find_in_deps)
|
||||
.or_else(|| deps_dev.and_then(find_in_deps))
|
||||
}
|
||||
|
||||
/// Returns the path for the crate with the given name.
|
||||
///
|
||||
/// This is a convenience method for constructing a [manifest] and
|
||||
/// calling the [`get_path`] method.
|
||||
///
|
||||
/// This method should only be used where you just need the path and can't
|
||||
/// cache the [manifest]. If caching is possible, it's recommended to create
|
||||
/// the [manifest] yourself and use the [`get_path`] method.
|
||||
///
|
||||
/// [`get_path`]: Self::get_path
|
||||
/// [manifest]: Self
|
||||
pub fn get_path_direct(name: &str) -> syn::Path {
|
||||
Self::default().get_path(name)
|
||||
}
|
||||
|
||||
pub fn get_path(&self, name: &str) -> syn::Path {
|
||||
self.maybe_get_path(name)
|
||||
.unwrap_or_else(|| Self::parse_str(name))
|
||||
}
|
||||
|
||||
pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T {
|
||||
syn::parse(path.parse::<TokenStream>().unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive a label trait
|
||||
///
|
||||
/// # Args
|
||||
///
|
||||
/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait
|
||||
/// - `trait_path`: The path [`syn::Path`] to the label trait
|
||||
pub fn derive_label(
|
||||
input: syn::DeriveInput,
|
||||
trait_path: &syn::Path,
|
||||
attr_name: &str,
|
||||
) -> TokenStream {
|
||||
// return true if the variant specified is an `ignore_fields` attribute
|
||||
fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool {
|
||||
if attr.path.get_ident().as_ref().unwrap() != &attr_name {
|
||||
return false;
|
||||
}
|
||||
|
||||
syn::custom_keyword!(ignore_fields);
|
||||
attr.parse_args_with(|input: syn::parse::ParseStream| {
|
||||
let ignore = input.parse::<Option<ignore_fields>>()?.is_some();
|
||||
Ok(ignore)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
let ident = input.ident.clone();
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
|
||||
where_token: Default::default(),
|
||||
predicates: Default::default(),
|
||||
});
|
||||
where_clause
|
||||
.predicates
|
||||
.push(syn::parse2(quote! { Self: 'static }).unwrap());
|
||||
|
||||
let as_str = match input.data {
|
||||
syn::Data::Struct(d) => {
|
||||
// see if the user tried to ignore fields incorrectly
|
||||
if let Some(attr) = d
|
||||
.fields
|
||||
.iter()
|
||||
.flat_map(|f| &f.attrs)
|
||||
.find(|a| is_ignore(a, attr_name))
|
||||
{
|
||||
let err_msg = format!("`#[{attr_name}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration");
|
||||
return quote_spanned! {
|
||||
attr.span() => compile_error!(#err_msg);
|
||||
}
|
||||
.into();
|
||||
}
|
||||
// Structs must either be fieldless, or explicitly ignore the fields.
|
||||
let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, attr_name));
|
||||
if matches!(d.fields, syn::Fields::Unit) || ignore_fields {
|
||||
let lit = ident.to_string();
|
||||
quote! { #lit }
|
||||
} else {
|
||||
let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`");
|
||||
return quote_spanned! {
|
||||
d.fields.span() => compile_error!(#err_msg);
|
||||
}
|
||||
.into();
|
||||
}
|
||||
}
|
||||
syn::Data::Enum(d) => {
|
||||
// check if the user put #[label(ignore_fields)] in the wrong place
|
||||
if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, attr_name)) {
|
||||
let err_msg = format!("`#[{attr_name}(ignore_fields)]` can only be applied to enum variants or struct declarations");
|
||||
return quote_spanned! {
|
||||
attr.span() => compile_error!(#err_msg);
|
||||
}
|
||||
.into();
|
||||
}
|
||||
let arms = d.variants.iter().map(|v| {
|
||||
// Variants must either be fieldless, or explicitly ignore the fields.
|
||||
let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, attr_name));
|
||||
if matches!(v.fields, syn::Fields::Unit) | ignore_fields {
|
||||
let mut path = syn::Path::from(ident.clone());
|
||||
path.segments.push(v.ident.clone().into());
|
||||
let lit = format!("{ident}::{}", v.ident.clone());
|
||||
quote! { #path { .. } => #lit }
|
||||
} else {
|
||||
let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`");
|
||||
quote_spanned! {
|
||||
v.fields.span() => _ => { compile_error!(#err_msg); }
|
||||
}
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
match self {
|
||||
#(#arms),*
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::Data::Union(_) => {
|
||||
return quote_spanned! {
|
||||
input.span() => compile_error!("Unions cannot be used as labels.");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
};
|
||||
|
||||
(quote! {
|
||||
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
|
||||
fn as_str(&self) -> &'static str {
|
||||
#as_str
|
||||
}
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
20
azalea-ecs/azalea-ecs-macros/src/utils/shape.rs
Normal file
20
azalea-ecs/azalea-ecs-macros/src/utils/shape.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use proc_macro::Span;
|
||||
use syn::{Data, DataStruct, Error, Fields, FieldsNamed};
|
||||
|
||||
/// Get the fields of a data structure if that structure is a struct with named fields;
|
||||
/// otherwise, return a compile error that points to the site of the macro invocation.
|
||||
pub fn get_named_struct_fields(data: &syn::Data) -> syn::Result<&FieldsNamed> {
|
||||
match data {
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Named(fields),
|
||||
..
|
||||
}) => Ok(fields),
|
||||
_ => Err(Error::new(
|
||||
// This deliberately points to the call site rather than the structure
|
||||
// body; marking the entire body as the source of the error makes it
|
||||
// impossible to figure out which `derive` has a problem.
|
||||
Span::call_site().into(),
|
||||
"Only structs with named fields are supported",
|
||||
)),
|
||||
}
|
||||
}
|
35
azalea-ecs/azalea-ecs-macros/src/utils/symbol.rs
Normal file
35
azalea-ecs/azalea-ecs-macros/src/utils/symbol.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use std::fmt::{self, Display};
|
||||
use syn::{Ident, Path};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Symbol(pub &'static str);
|
||||
|
||||
impl PartialEq<Symbol> for Ident {
|
||||
fn eq(&self, word: &Symbol) -> bool {
|
||||
self == word.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<Symbol> for &'a Ident {
|
||||
fn eq(&self, word: &Symbol) -> bool {
|
||||
*self == word.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Symbol> for Path {
|
||||
fn eq(&self, word: &Symbol) -> bool {
|
||||
self.is_ident(word.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<Symbol> for &'a Path {
|
||||
fn eq(&self, word: &Symbol) -> bool {
|
||||
self.is_ident(word.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Symbol {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str(self.0)
|
||||
}
|
||||
}
|
97
azalea-ecs/src/lib.rs
Normal file
97
azalea-ecs/src/lib.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
#![feature(trait_alias)]
|
||||
|
||||
//! Re-export the necessary parts of `bevy_ecs` and `bevy_app`.
|
||||
//!
|
||||
//! This is completely compatible with `bevy_ecs`, so it won't cause issues if
|
||||
//! you use plugins meant for Bevy.
|
||||
|
||||
use std::{
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub mod component {
|
||||
pub use azalea_ecs_macros::Component;
|
||||
pub use bevy_ecs::component::{ComponentStorage, TableStorage};
|
||||
|
||||
// we do this because re-exporting Component would re-export the macro as well,
|
||||
// which is bad (since we have our own Component macro)
|
||||
// instead, we have to do this so Component is a trait alias and the original
|
||||
// impl-able trait is still available as BevyComponent
|
||||
pub trait Component = bevy_ecs::component::Component;
|
||||
pub use bevy_ecs::component::Component as BevyComponent;
|
||||
}
|
||||
pub mod ecs {
|
||||
pub use bevy_ecs::world::World as Ecs;
|
||||
pub use bevy_ecs::world::{EntityMut, EntityRef, Mut};
|
||||
}
|
||||
pub use bevy_app::*;
|
||||
use bevy_ecs::schedule::*;
|
||||
pub use bevy_ecs::system;
|
||||
pub use bevy_ecs::{event, query, schedule};
|
||||
use ecs::Ecs;
|
||||
use futures::task::noop_waker_ref;
|
||||
use tokio::time::Interval;
|
||||
|
||||
pub struct TickPlugin;
|
||||
impl Plugin for TickPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_stage_before(
|
||||
CoreStage::Update,
|
||||
TickLabel,
|
||||
TickStage::from_stage(SystemStage::parallel()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(StageLabel)]
|
||||
struct TickLabel;
|
||||
|
||||
/// A [`Stage`] that runs every 50 milliseconds.
|
||||
pub struct TickStage {
|
||||
pub interval: Interval,
|
||||
stage: Box<dyn Stage>,
|
||||
}
|
||||
|
||||
impl TickStage {
|
||||
pub fn from_stage(stage: impl Stage) -> Self {
|
||||
let mut game_tick_interval = tokio::time::interval(Duration::from_millis(50));
|
||||
// TODO: Minecraft bursts up to 10 ticks and then skips, we should too
|
||||
game_tick_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Burst);
|
||||
|
||||
TickStage {
|
||||
interval: game_tick_interval,
|
||||
stage: Box::new(stage),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Stage for TickStage {
|
||||
fn run(&mut self, ecs: &mut Ecs) {
|
||||
// keep calling run until it's caught up
|
||||
while let Poll::Ready(r) = self
|
||||
.interval
|
||||
.poll_tick(&mut Context::from_waker(&noop_waker_ref()))
|
||||
{
|
||||
self.stage.run(ecs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AppTickExt {
|
||||
fn add_tick_system_set(&mut self, system_set: SystemSet) -> &mut App;
|
||||
}
|
||||
|
||||
impl AppTickExt for App {
|
||||
fn add_tick_system_set(&mut self, system_set: SystemSet) -> &mut App {
|
||||
let tick_stage = self
|
||||
.schedule
|
||||
.get_stage_mut::<TickStage>(TickLabel)
|
||||
.expect("Tick Stage not found");
|
||||
let stage = tick_stage
|
||||
.stage
|
||||
.downcast_mut::<SystemStage>()
|
||||
.expect("Fixed Timestep sub-stage is not a SystemStage");
|
||||
stage.add_system_set(system_set);
|
||||
self
|
||||
}
|
||||
}
|
|
@ -15,34 +15,33 @@ use bevy_app::Plugin;
|
|||
use bevy_ecs::{
|
||||
event::{EventReader, EventWriter},
|
||||
query::With,
|
||||
schedule::IntoSystemDescriptor,
|
||||
schedule::{IntoSystemDescriptor, SystemStage},
|
||||
system::Res,
|
||||
};
|
||||
use bevy_ecs::{schedule::SystemSet, system::Query};
|
||||
use collision::{move_colliding, MoverType};
|
||||
use iyes_loopless::prelude::*;
|
||||
|
||||
pub struct PhysicsPlugin;
|
||||
impl Plugin for PhysicsPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.add_event::<ForceJumpEvent>()
|
||||
.add_fixed_timestep_system_set(
|
||||
"tick",
|
||||
0,
|
||||
SystemSet::new()
|
||||
.with_system(ai_step.label("ai_step"))
|
||||
.with_system(
|
||||
force_jump_listener
|
||||
.label("force_jump_listener")
|
||||
.after("ai_step"),
|
||||
)
|
||||
.with_system(
|
||||
travel
|
||||
.label("travel")
|
||||
.after("ai_step")
|
||||
.after("force_jump_listener"),
|
||||
),
|
||||
);
|
||||
.stage("tick", |stage: &mut SystemStage| {
|
||||
stage.add_system_set(
|
||||
SystemSet::new()
|
||||
.with_system(ai_step.label("ai_step"))
|
||||
.with_system(
|
||||
force_jump_listener
|
||||
.label("force_jump_listener")
|
||||
.after("ai_step"),
|
||||
)
|
||||
.with_system(
|
||||
travel
|
||||
.label("travel")
|
||||
.after("ai_step")
|
||||
.after("force_jump_listener"),
|
||||
),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue