Skip to content

Commit b64736f

Browse files
Removal
1 parent 49a4737 commit b64736f

File tree

6 files changed

+203
-95
lines changed

6 files changed

+203
-95
lines changed

crates/bevy_sprite/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
22
#![cfg_attr(docsrs, feature(doc_cfg))]
3-
#![forbid(unsafe_code)]
43
#![doc(
54
html_logo_url = "https://bevy.org/assets/icon.png",
65
html_favicon_url = "https://bevy.org/assets/icon.png"
Lines changed: 100 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
use std::marker::PhantomData;
22

3-
use crate::tilemap::{TileData, TileStorage, Tilemap};
4-
use bevy_ecs::{entity::Entity, hierarchy::ChildOf, system::{Command, Commands}, world::World};
3+
use crate::{
4+
tilemap::{TileData, TileStorage, Tilemap},
5+
TileStorages,
6+
};
7+
use bevy_ecs::{
8+
change_detection::DetectChangesMut,
9+
entity::Entity,
10+
hierarchy::ChildOf,
11+
system::{Command, Commands},
12+
world::World,
13+
};
514
use bevy_math::{IVec2, Vec2, Vec3};
615
use bevy_transform::components::Transform;
716

@@ -13,7 +22,7 @@ pub trait CommandsTilemapExt {
1322
maybe_tile: Option<T>,
1423
);
1524

16-
fn remove_tile<T: TileData>(&mut self, tilemap_id: Entity, tile_position: IVec2);
25+
fn remove_tile(&mut self, tilemap_id: Entity, tile_position: IVec2);
1726
}
1827

1928
impl CommandsTilemapExt for Commands<'_, '_> {
@@ -23,11 +32,24 @@ impl CommandsTilemapExt for Commands<'_, '_> {
2332
tile_position: IVec2,
2433
maybe_tile: Option<T>,
2534
) {
26-
self.queue(move |world: &mut World| {SetTile {tilemap_id, tile_position, maybe_tile }.apply(world);});
35+
self.queue(move |world: &mut World| {
36+
SetTile {
37+
tilemap_id,
38+
tile_position,
39+
maybe_tile,
40+
}
41+
.apply(world);
42+
});
2743
}
2844

29-
fn remove_tile<T: TileData>(&mut self, tilemap_id: Entity, tile_position: IVec2) {
30-
self.queue(move |world: &mut World| {RemoveTile::<T> {tilemap_id, tile_position, _t: PhantomData::default() }.apply(world);});
45+
fn remove_tile(&mut self, tilemap_id: Entity, tile_position: IVec2) {
46+
self.queue(move |world: &mut World| {
47+
RemoveTile {
48+
tilemap_id,
49+
tile_position,
50+
}
51+
.apply(world);
52+
});
3153
}
3254
}
3355

@@ -44,107 +66,115 @@ pub struct SetTileResult<T: TileData> {
4466

4567
impl<T: TileData> Default for SetTileResult<T> {
4668
fn default() -> Self {
47-
Self { replaced_tile: Default::default(), chunk_id: Default::default() }
69+
Self {
70+
replaced_tile: Default::default(),
71+
chunk_id: Default::default(),
72+
}
4873
}
4974
}
5075

5176
impl<T: TileData> Command<SetTileResult<T>> for SetTile<T> {
5277
fn apply(self, world: &mut World) -> SetTileResult<T> {
53-
let Ok(mut tilemap_entity) = world.get_entity_mut(self.tilemap_id) else {
54-
tracing::warn!("Could not find Tilemap Entity {:?}", self.tilemap_id);
55-
return Default::default();
56-
};
57-
58-
let Some(tilemap) = tilemap_entity.get::<Tilemap>() else {
59-
tracing::warn!("Could not find Tilemap on Entity {:?}", self.tilemap_id);
60-
return Default::default();
61-
};
78+
let Ok(mut tilemap_entity) = world.get_entity_mut(self.tilemap_id) else {
79+
tracing::warn!("Could not find Tilemap Entity {:?}", self.tilemap_id);
80+
return Default::default();
81+
};
6282

63-
let chunk_position = tilemap.tile_chunk_position(self.tile_position);
64-
let tile_relative_position = tilemap.tile_relative_position(self.tile_position);
83+
let Some(tilemap) = tilemap_entity.get::<Tilemap>() else {
84+
tracing::warn!("Could not find Tilemap on Entity {:?}", self.tilemap_id);
85+
return Default::default();
86+
};
6587

66-
if let Some(tile_storage_id) = tilemap.chunks.get(&chunk_position).cloned() {
67-
let replaced_tile = tilemap_entity.world_scope(move |w| {
68-
let Ok(mut tilestorage_entity) = w.get_entity_mut(tile_storage_id) else {
69-
tracing::warn!("Could not find TileStorage Entity {:?}", tile_storage_id);
70-
return None;
71-
};
88+
let chunk_position = tilemap.tile_chunk_position(self.tile_position);
89+
let tile_relative_position = tilemap.tile_relative_position(self.tile_position);
7290

73-
let Some(mut tile_storage) = tilestorage_entity.get_mut::<TileStorage<T>>()
74-
else {
75-
tracing::warn!(
76-
"Could not find TileStorage on Entity {:?}",
77-
tile_storage_id
78-
);
79-
return None;
80-
};
91+
if let Some(tile_storage_id) = tilemap.chunks.get(&chunk_position).cloned() {
92+
let replaced_tile = tilemap_entity.world_scope(move |w| {
93+
let Ok(mut tilestorage_entity) = w.get_entity_mut(tile_storage_id) else {
94+
tracing::warn!("Could not find TileStorage Entity {:?}", tile_storage_id);
95+
return None;
96+
};
8197

82-
tile_storage.set(tile_relative_position, self.maybe_tile)
83-
});
84-
SetTileResult { chunk_id: Some(tile_storage_id), replaced_tile }
85-
} else {
86-
let chunk_size = tilemap.chunk_size;
87-
let tile_size = tilemap.tile_display_size;
88-
let tile_storage_id = tilemap_entity.world_scope(move |w| {
89-
let mut tile_storage = TileStorage::<T>::new(chunk_size);
90-
tile_storage.set(tile_relative_position, self.maybe_tile);
91-
let translation = Vec2::new(chunk_size.x as f32, chunk_size.y as f32) * Vec2::new(tile_size.x as f32, tile_size.y as f32) * Vec2::new(chunk_position.x as f32, chunk_position.y as f32);
92-
let translation = Vec3::new(translation.x, translation.y, 0.0);
93-
let transform = Transform::from_translation(translation);
94-
w.spawn((ChildOf(self.tilemap_id), tile_storage, transform)).id()
95-
});
96-
let Some(mut tilemap) = tilemap_entity.get_mut::<Tilemap>() else {
97-
tracing::warn!("Could not find Tilemap on Entity {:?}", self.tilemap_id);
98-
return Default::default();
98+
let Some(mut tile_storage) = tilestorage_entity.get_mut::<TileStorage<T>>() else {
99+
tracing::warn!("Could not find TileStorage on Entity {:?}", tile_storage_id);
100+
return None;
99101
};
100-
tilemap.chunks.insert(chunk_position, tile_storage_id);
101-
SetTileResult { chunk_id: Some(tile_storage_id), replaced_tile: None }
102+
103+
tile_storage.set(tile_relative_position, self.maybe_tile)
104+
});
105+
SetTileResult {
106+
chunk_id: Some(tile_storage_id),
107+
replaced_tile,
108+
}
109+
} else {
110+
let chunk_size = tilemap.chunk_size;
111+
let tile_size = tilemap.tile_display_size;
112+
let tile_storage_id = tilemap_entity.world_scope(move |w| {
113+
let mut tile_storage = TileStorage::<T>::new(chunk_size);
114+
tile_storage.set(tile_relative_position, self.maybe_tile);
115+
let translation = Vec2::new(chunk_size.x as f32, chunk_size.y as f32)
116+
* Vec2::new(tile_size.x as f32, tile_size.y as f32)
117+
* Vec2::new(chunk_position.x as f32, chunk_position.y as f32);
118+
let translation = Vec3::new(translation.x, translation.y, 0.0);
119+
let transform = Transform::from_translation(translation);
120+
w.spawn((ChildOf(self.tilemap_id), tile_storage, transform))
121+
.id()
122+
});
123+
let Some(mut tilemap) = tilemap_entity.get_mut::<Tilemap>() else {
124+
tracing::warn!("Could not find Tilemap on Entity {:?}", self.tilemap_id);
125+
return Default::default();
126+
};
127+
tilemap.chunks.insert(chunk_position, tile_storage_id);
128+
SetTileResult {
129+
chunk_id: Some(tile_storage_id),
130+
replaced_tile: None,
102131
}
132+
}
103133
}
104134
}
105135

106-
pub struct RemoveTile<T: TileData> {
136+
pub struct RemoveTile {
107137
pub tilemap_id: Entity,
108138
pub tile_position: IVec2,
109-
pub _t: PhantomData<T>
110139
}
111140

112-
impl<T: TileData> Command<Option<T>> for RemoveTile<T> {
113-
fn apply(self, world: &mut World) -> Option<T> {
141+
impl Command for RemoveTile {
142+
fn apply(self, world: &mut World) {
114143
let Ok(mut tilemap_entity) = world.get_entity_mut(self.tilemap_id) else {
115144
tracing::warn!("Could not find Tilemap Entity {:?}", self.tilemap_id);
116-
return Default::default();
145+
return;
117146
};
118147

119148
let Some(tilemap) = tilemap_entity.get::<Tilemap>() else {
120149
tracing::warn!("Could not find Tilemap on Entity {:?}", self.tilemap_id);
121-
return Default::default();
150+
return;
122151
};
123152

124153
let chunk_position = tilemap.tile_chunk_position(self.tile_position);
125154
let tile_relative_position = tilemap.tile_relative_position(self.tile_position);
126-
155+
127156
if let Some(tile_storage_id) = tilemap.chunks.get(&chunk_position).cloned() {
128157
tilemap_entity.world_scope(move |w| {
129-
let Ok(mut tilestorage_entity) = w.get_entity_mut(tile_storage_id) else {
158+
let Ok(mut tile_storage_entity) = w.get_entity_mut(tile_storage_id) else {
130159
tracing::warn!("Could not find TileStorage Entity {:?}", tile_storage_id);
131-
return None;
160+
return;
132161
};
133162

134-
let Some(mut tile_storage) = tilestorage_entity.get_mut::<TileStorage<T>>()
135-
else {
163+
let Some(tile_storages) = tile_storage_entity.get::<TileStorages>().cloned() else {
136164
tracing::warn!(
137-
"Could not find TileStorage on Entity {:?}",
165+
"Could not find TileStorages on Entity {:?}",
138166
tile_storage_id
139167
);
140-
return None;
168+
return;
141169
};
142170

143-
tile_storage.remove(tile_relative_position)
144-
})
145-
}
146-
else {
147-
None
171+
for (tile_storage, tile_removal) in tile_storages.removals {
172+
let Ok(storage) = tile_storage_entity.get_mut_by_id(tile_storage) else {
173+
continue;
174+
};
175+
tile_removal(storage, tile_relative_position);
176+
}
177+
});
148178
}
149179
}
150-
}
180+
}

crates/bevy_sprite/src/tilemap/entity_tiles.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::marker::PhantomData;
2-
31
use bevy_app::{App, Plugin};
42
use bevy_derive::Deref;
53
use bevy_ecs::{component::Component, entity::Entity, hierarchy::ChildOf, lifecycle::HookContext, system::Command, world::{DeferredWorld, World}};
@@ -93,16 +91,12 @@ fn on_remove_entity_tile(mut world: DeferredWorld, HookContext { entity, .. }: H
9391
world
9492
.commands()
9593
.queue(move |world: &mut World| {
96-
let Some(removed) = RemoveTile::<EntityTile> {
94+
RemoveTile {
9795
tilemap_id: in_map.0,
9896
tile_position: tile_position.0,
99-
_t: PhantomData,
100-
}.apply(world) else {
101-
warn!("Tile {} could not be removed from map or was already removed.", entity);
102-
return;
103-
};
97+
}.apply(world);
10498

105-
let mut removed = world.entity_mut(removed.0);
99+
let mut removed = world.entity_mut(entity);
106100
if removed.contains::<DespawnOnRemove>() {
107101
removed.despawn();
108102
} else {

crates/bevy_sprite/src/tilemap/storage.rs

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
11
use std::ops::Deref;
22

3-
use bevy_ecs::{component::Component, entity::Entity, name::Name, reflect::ReflectComponent};
3+
use bevy_ecs::{
4+
change_detection::{DetectChangesMut, MutUntyped},
5+
component::{Component, ComponentId},
6+
entity::Entity,
7+
lifecycle::HookContext,
8+
name::Name,
9+
ptr::PtrMut,
10+
reflect::ReflectComponent,
11+
world::{DeferredWorld, World},
12+
};
413
use bevy_math::{URect, UVec2};
14+
use bevy_platform::collections::{HashMap, HashSet};
515
use bevy_reflect::Reflect;
616
use bevy_transform::components::Transform;
17+
use tracing::error;
18+
19+
use crate::TileData;
20+
21+
#[derive(Component, Clone, Debug, Default)]
22+
#[require(Name::new("TileStorage"), Transform)]
23+
pub struct TileStorages {
24+
// Stores removal operations
25+
pub(crate) removals: HashMap<ComponentId, fn(MutUntyped<'_>, UVec2)>,
26+
}
727

828
#[derive(Component, Clone, Debug, Default, Reflect)]
929
#[reflect(Component)]
10-
#[require(Name::new("TileStorage"), Transform)]
11-
pub struct TileStorage<T> {
30+
#[require(Name::new("TileStorage"), TileStorages, Transform)]
31+
#[component(on_add = on_add_tile_storage::<T>)]
32+
pub struct TileStorage<T: TileData> {
1233
pub tiles: Vec<Option<T>>,
1334
size: UVec2,
1435
}
1536

16-
impl<T> TileStorage<T> {
37+
impl<T: TileData> TileStorage<T> {
1738
pub fn new(size: UVec2) -> Self {
1839
let mut tiles = Vec::new();
1940
tiles.resize_with(size.element_product() as usize, Default::default);
@@ -80,3 +101,43 @@ impl<T> TileStorage<T> {
80101
self.size
81102
}
82103
}
104+
105+
fn on_add_tile_storage<T: TileData>(
106+
mut world: DeferredWorld<'_>,
107+
HookContext {
108+
component_id,
109+
entity,
110+
..
111+
}: HookContext,
112+
) {
113+
world.commands().queue(move |world: &mut World| {
114+
let Ok(mut tile_storage_entity) = world.get_entity_mut(entity) else {
115+
error!("Could not fine Tile Storage {}", entity);
116+
return;
117+
};
118+
119+
if let Some(mut storages) = tile_storage_entity.get_mut::<TileStorages>() {
120+
storages.removals.insert(component_id, remove_tile::<T>);
121+
} else {
122+
let mut tile_storages = TileStorages {
123+
removals: HashMap::with_capacity(1),
124+
};
125+
tile_storages
126+
.removals
127+
.insert(component_id, remove_tile::<T>);
128+
tile_storage_entity.insert(tile_storages);
129+
}
130+
})
131+
}
132+
133+
fn remove_tile<T: TileData>(mut raw: MutUntyped<'_>, tile_coord: UVec2) {
134+
#[expect(unsafe_code, reason = "testing")]
135+
let storage = unsafe {
136+
raw.bypass_change_detection()
137+
.reborrow()
138+
.deref_mut::<TileStorage<T>>()
139+
};
140+
if storage.remove(tile_coord).is_some() {
141+
raw.set_changed();
142+
}
143+
}

crates/bevy_sprite_render/src/tilemap_chunk/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,10 @@ fn on_remove_tile_render_data(mut world: DeferredWorld, HookContext { entity, ..
344344
world
345345
.commands()
346346
.queue(move |world: &mut World| {
347-
RemoveTile::<TileRenderData> {
347+
SetTile::<TileRenderData> {
348348
tilemap_id: in_map.0,
349349
tile_position: tile_position.0,
350-
_t: PhantomData::default(),
350+
maybe_tile: None,
351351
}.apply(world);
352352
});
353353
}

0 commit comments

Comments
 (0)