feat: data-oriented chunks optimization with tile registry
- Phase 1: Bit-packed standability (ChunkData with bitsets) - Phase 2: Reactive connectivity (dirty chunks) - Phase 3: Async terrain baking (AsyncComputeTaskPool) - Pathfinding weight system (rock=50 preferred, bedrock=150 avoided) - Movement speed affected by tile weight - External tiles.toml for hot-reloadable tile definitions - TileRegistry singleton for async-safe tile lookups - Fixed world_to_chunk to use CHUNK_SIZE_TILE (128) not CHUNK_SIZE (8) - Fixed infinite spawner with Local<bool> state guards - Fixed spawn coordinate grid alignment Note: Zigzag pathfinding bug introduced - needs investigation
This commit is contained in:
@@ -13,8 +13,8 @@ pub const CHUNK_SIZE_TILE: i32 = CHUNK_SIZE * crate::constants::ITILE_SIZE;
|
||||
#[inline]
|
||||
pub fn world_to_chunk(world_pos: IVec3) -> IVec2 {
|
||||
IVec2::new(
|
||||
world_pos.x.div_euclid(CHUNK_SIZE),
|
||||
world_pos.y.div_euclid(CHUNK_SIZE),
|
||||
world_pos.x.div_euclid(CHUNK_SIZE_TILE),
|
||||
world_pos.y.div_euclid(CHUNK_SIZE_TILE),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+110
-13
@@ -1,11 +1,11 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::tasks::AsyncComputeTaskPool;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform::time::Instant;
|
||||
use noise::{NoiseFn, Perlin};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::{
|
||||
config::TileRegistry,
|
||||
constants::{SEED, TILE_SIZE},
|
||||
world::{
|
||||
tiles::{ChunkData, FloorTileData, TileMap, TerrainSpriteState, CurrentWorldSpriteState},
|
||||
@@ -70,6 +70,7 @@ fn generate_terrain_blob(chunk_pos: IVec2) -> TerrainBlob {
|
||||
let cave_noise = Perlin::new(SEED);
|
||||
let start_x = chunk_pos.x * CHUNK_SIZE;
|
||||
let start_y = chunk_pos.y * CHUNK_SIZE;
|
||||
let registry = TileRegistry::global();
|
||||
|
||||
let mut chunk_data = ChunkData::new(chunk_pos);
|
||||
let mut tile_updates: Vec<(IVec3, FloorTileData)> = Vec::new();
|
||||
@@ -105,51 +106,147 @@ fn generate_terrain_blob(chunk_pos: IVec2) -> TerrainBlob {
|
||||
z as f64 * 0.05,
|
||||
]);
|
||||
if cave_value < -0.75 {
|
||||
let tile = registry.floor("air");
|
||||
tile_spawns.push((position, FloorTilePrefab::air(position)));
|
||||
tile_updates.push((
|
||||
pos_ivec,
|
||||
FloorTileData::new(0, true, false, true, 0, [0; 8]),
|
||||
FloorTileData::new(
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(local_x, local_y, local_z, 0, true, false);
|
||||
chunk_data.set_floor_tile(
|
||||
local_x,
|
||||
local_y,
|
||||
local_z,
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.astar_weight,
|
||||
);
|
||||
} else if cave_value < 0.8 {
|
||||
let tile = registry.floor("rock");
|
||||
tile_spawns.push((position, FloorTilePrefab::rock(position)));
|
||||
tile_updates.push((
|
||||
pos_ivec,
|
||||
FloorTileData::new(2, false, true, false, 50, [0; 8]),
|
||||
FloorTileData::new(
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(local_x, local_y, local_z, 2, false, true);
|
||||
chunk_data.set_floor_tile(
|
||||
local_x,
|
||||
local_y,
|
||||
local_z,
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.astar_weight,
|
||||
);
|
||||
} else {
|
||||
let tile = registry.floor("dirt");
|
||||
tile_spawns.push((position, FloorTilePrefab::dirt(position)));
|
||||
tile_updates.push((
|
||||
pos_ivec,
|
||||
FloorTileData::new(1, false, true, false, 85, [0; 8]),
|
||||
FloorTileData::new(
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(local_x, local_y, local_z, 1, false, true);
|
||||
chunk_data.set_floor_tile(
|
||||
local_x,
|
||||
local_y,
|
||||
local_z,
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.astar_weight,
|
||||
);
|
||||
}
|
||||
} else if noise_position.z > position.z {
|
||||
if surface_height <= position.z + TILE_SIZE {
|
||||
let tile = registry.floor("grass");
|
||||
tile_spawns.push((position, FloorTilePrefab::grass(position)));
|
||||
tile_updates.push((
|
||||
pos_ivec,
|
||||
FloorTileData::new(1, false, true, false, 100, [0; 8]),
|
||||
FloorTileData::new(
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(local_x, local_y, local_z, 1, false, true);
|
||||
chunk_data.set_floor_tile(
|
||||
local_x,
|
||||
local_y,
|
||||
local_z,
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.astar_weight,
|
||||
);
|
||||
surface_positions.push((position, "grass".to_string()));
|
||||
} else {
|
||||
let tile = registry.floor("dirt");
|
||||
tile_spawns.push((position, FloorTilePrefab::dirt(position)));
|
||||
tile_updates.push((
|
||||
pos_ivec,
|
||||
FloorTileData::new(1, false, true, false, 85, [0; 8]),
|
||||
FloorTileData::new(
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(local_x, local_y, local_z, 1, false, true);
|
||||
chunk_data.set_floor_tile(
|
||||
local_x,
|
||||
local_y,
|
||||
local_z,
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.astar_weight,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let tile = registry.floor("air");
|
||||
tile_spawns.push((position, FloorTilePrefab::air(position)));
|
||||
tile_updates.push((
|
||||
pos_ivec,
|
||||
FloorTileData::new(0, true, false, true, 0, [0; 8]),
|
||||
FloorTileData::new(
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(local_x, local_y, local_z, 0, true, false);
|
||||
chunk_data.set_floor_tile(
|
||||
local_x,
|
||||
local_y,
|
||||
local_z,
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.astar_weight,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ pub struct ChunkData {
|
||||
|
||||
/// Tile IDs for rendering.
|
||||
pub tile_ids: Vec<u8>,
|
||||
|
||||
/// A* pathfinding weights per tile. Lower = better path.
|
||||
/// Weight 0 = impassable, weight 50 = fast (rock), weight 100 = normal (grass), weight 150 = slow (bedrock).
|
||||
pub astar_weights: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ChunkData {
|
||||
@@ -57,6 +61,7 @@ impl ChunkData {
|
||||
stand_in_fixture: [0u32; BITSET_WORDS],
|
||||
stand_on_fixture: [0u32; BITSET_WORDS],
|
||||
tile_ids: vec![0u8; TOTAL_TILES],
|
||||
astar_weights: vec![100u8; TOTAL_TILES],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +189,7 @@ impl ChunkData {
|
||||
tile_id: u8,
|
||||
can_stand_in: bool,
|
||||
can_stand_on: bool,
|
||||
astar_weight: u8,
|
||||
) {
|
||||
let idx = Self::pos_to_index(local_x, local_y, z);
|
||||
let word = idx / 32;
|
||||
@@ -203,6 +209,20 @@ impl ChunkData {
|
||||
}
|
||||
|
||||
self.tile_ids[idx] = tile_id;
|
||||
self.astar_weights[idx] = astar_weight;
|
||||
}
|
||||
|
||||
/// Get A* weight at position. Returns 100 (default) if out of bounds.
|
||||
#[inline]
|
||||
pub fn get_astar_weight(&self, local_x: i32, local_y: i32, z: i32) -> u8 {
|
||||
if local_x < 0 || local_x >= CHUNK_SIZE || local_y < 0 || local_y >= CHUNK_SIZE {
|
||||
return 100;
|
||||
}
|
||||
if z < -(Z_BELOW as i32) || z > (Z_ABOVE as i32) {
|
||||
return 100;
|
||||
}
|
||||
let idx = Self::pos_to_index(local_x, local_y, z);
|
||||
self.astar_weights[idx]
|
||||
}
|
||||
|
||||
/// Set only fixture standability bits (for forestry generation).
|
||||
@@ -253,6 +273,7 @@ impl ChunkData {
|
||||
self.stand_in_fixture.fill(0);
|
||||
self.stand_on_fixture.fill(0);
|
||||
self.tile_ids.fill(0);
|
||||
self.astar_weights.fill(100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+56
-29
@@ -1,8 +1,11 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::world::{
|
||||
tiles::{FixtureTile, FloorTile, TileState},
|
||||
FIXTURE_ID_OFFSET,
|
||||
use crate::{
|
||||
config::TileRegistry,
|
||||
world::{
|
||||
tiles::{FixtureTile, FloorTile, TileState},
|
||||
FIXTURE_ID_OFFSET,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Bundle)]
|
||||
@@ -15,12 +18,15 @@ pub struct FloorTilePrefab {
|
||||
|
||||
impl FloorTilePrefab {
|
||||
pub fn grass(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().floor("grass");
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: 1,
|
||||
astar_weight: 100,
|
||||
..Default::default()
|
||||
id: def.id as u32,
|
||||
opaque: !def.transparent,
|
||||
walkable: def.can_stand_on,
|
||||
astar_weight: def.astar_weight,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
@@ -30,12 +36,15 @@ impl FloorTilePrefab {
|
||||
}
|
||||
|
||||
pub fn dirt(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().floor("dirt");
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: 2,
|
||||
astar_weight: 85,
|
||||
..Default::default()
|
||||
id: def.id as u32,
|
||||
opaque: !def.transparent,
|
||||
walkable: def.can_stand_on,
|
||||
astar_weight: def.astar_weight,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
@@ -45,12 +54,15 @@ impl FloorTilePrefab {
|
||||
}
|
||||
|
||||
pub fn rock(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().floor("rock");
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: 3,
|
||||
astar_weight: 50,
|
||||
..Default::default()
|
||||
id: def.id as u32,
|
||||
opaque: !def.transparent,
|
||||
walkable: def.can_stand_on,
|
||||
astar_weight: def.astar_weight,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
@@ -60,13 +72,15 @@ impl FloorTilePrefab {
|
||||
}
|
||||
|
||||
pub fn air(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().floor("air");
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: 0,
|
||||
opaque: false,
|
||||
walkable: false,
|
||||
..Default::default()
|
||||
id: def.id as u32,
|
||||
opaque: !def.transparent,
|
||||
walkable: def.can_stand_on,
|
||||
astar_weight: def.astar_weight,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
@@ -76,12 +90,15 @@ impl FloorTilePrefab {
|
||||
}
|
||||
|
||||
pub fn bedrock(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().floor("bedrock");
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: 4,
|
||||
astar_weight: 150,
|
||||
..Default::default()
|
||||
id: def.id as u32,
|
||||
opaque: !def.transparent,
|
||||
walkable: def.can_stand_on,
|
||||
astar_weight: def.astar_weight,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
@@ -104,55 +121,65 @@ pub struct FixtureTilePrefab {
|
||||
|
||||
impl FixtureTilePrefab {
|
||||
pub fn dirt_wall(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().fixture("dirt_wall");
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + 1,
|
||||
..Default::default()
|
||||
id: FIXTURE_ID_OFFSET + def.id,
|
||||
solid: def.solid,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rock_wall(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().fixture("rock_wall");
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + 2,
|
||||
..Default::default()
|
||||
id: FIXTURE_ID_OFFSET + def.id,
|
||||
solid: def.solid,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().fixture("log");
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + 4,
|
||||
..Default::default()
|
||||
id: FIXTURE_ID_OFFSET + def.id,
|
||||
solid: def.solid,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn leaves(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().fixture("leaves");
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + 5,
|
||||
..Default::default()
|
||||
id: FIXTURE_ID_OFFSET + def.id,
|
||||
solid: def.solid,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bedrock_wall(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().fixture("bedrock_wall");
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: 0,
|
||||
..Default::default()
|
||||
id: FIXTURE_ID_OFFSET + def.id,
|
||||
solid: def.solid,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
|
||||
@@ -269,4 +269,18 @@ impl TileMap {
|
||||
|
||||
(can_stand_in_floor || can_stand_in_fixture) && (can_stand_on_floor || can_stand_on_fixture)
|
||||
}
|
||||
|
||||
/// Get A* pathfinding weight for a tile position.
|
||||
/// Returns 100 (default) if tile not found. Lower is better.
|
||||
pub fn get_astar_weight(&self, world_pos: IVec3) -> u8 {
|
||||
let chunk_pos = world_to_chunk(world_pos);
|
||||
if let Some(chunk) = self.chunks.get(&chunk_pos) {
|
||||
let (local_x, local_y, z) = ChunkData::world_to_local(world_pos);
|
||||
return chunk.get_astar_weight(local_x, local_y, z);
|
||||
}
|
||||
self.floor_tiles
|
||||
.get(&world_pos)
|
||||
.map(|t| t.astar_weight)
|
||||
.unwrap_or(100)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user