diff --git a/src/crops/ambient.toml b/src/crops/ambient.toml index 6427091..5941fdb 100644 --- a/src/crops/ambient.toml +++ b/src/crops/ambient.toml @@ -53,11 +53,23 @@ name = "MediumCrop" attributes = ["Debuggable"] description = "A tag for instantiated medium crop entities." -[components."crops::medium_crop_occupant"] -type = "EntityId" +[components."crops::medium_crop_occupants"] +type = { type = "Vec", element_type = "EntityId" } name = "MediumCropOccupant" attributes = ["Debuggable"] -description = "A reference to the medium crop that occupies this tile. Can be null for no occupant." +description = """ +A list of the medium crops occupying this chunk. +Each element may be null if there is no occupant. +""" + +[components."crops::last_medium_crop_occupants"] +type = { type = "Vec", element_type = "EntityId" } +name = "MediumCropOccupant" +attributes = ["Debuggable"] +description = """ +A list of the medium crops occupying this chunk during the last network update. +This is diffed against medium_crop_occupants to get new network updates. +""" [components."crops::class"] type = "EntityId" @@ -65,12 +77,6 @@ name = "Class" attributes = ["Debuggable"] description = "A reference to the crop class that this crop is an instance of." -[components."crops::on_tile"] -type = "EntityId" -name = "OnTile" -attributes = ["Debuggable"] -description = "A reference to the tile that this crop is on." - [messages.update_medium_crops] name = "UpdateMediumCrops" description = "Server-to-client batch update for the medium crops in a chunk." diff --git a/src/crops/client.rs b/src/crops/client.rs index d0c1c98..7378f3c 100644 --- a/src/crops/client.rs +++ b/src/crops/client.rs @@ -1,7 +1,6 @@ use ambient_api::{ components::core::{ prefab::prefab_from_url, - primitives::cube, transform::{local_to_parent, local_to_world, rotation, translation}, }, concepts::make_transformable, @@ -10,7 +9,7 @@ use ambient_api::{ use components::{ crops::*, - map::{chunk_tile_index, chunk_tile_refs, in_chunk, position}, + map::{chunk_tile_index, in_chunk, position}, terrain::height, }; use flowerpot::CHUNK_SIZE; @@ -20,6 +19,14 @@ mod shared; #[main] fn main() { + spawn_query(components::map::chunk()).bind(move |entities| { + for (e, _) in entities { + let tile_num = CHUNK_SIZE * CHUNK_SIZE; + let occupants = vec![EntityId::null(); tile_num]; + entity::add_component(e, medium_crop_occupants(), occupants.clone()); + } + }); + let chunks = flowerpot::init_map(components::map::chunk()); UpdateMediumCrops::subscribe({ @@ -34,6 +41,11 @@ fn main() { break Some(chunk); } } else { + eprintln!( + "warning: dropping medium crop update on unloaded chunk {}", + data.chunk + ); + break None; } @@ -41,20 +53,21 @@ fn main() { }; let Some(chunk) = chunk else { return }; - let Some(tiles) = entity::get_component(chunk, chunk_tile_refs()) else { return }; + let Some(mut occupants) = entity::get_component(chunk, medium_crop_occupants()) else { return }; + + eprintln!("update: {:#?}", data.crop_tiles); for (tile_idx, class) in data .crop_tiles .into_iter() .zip(data.crop_classes.into_iter()) { - let tile = tiles[tile_idx as usize]; + eprintln!("updating {} with {:?}", tile_idx, class); - if let Some(old_occupant) = entity::get_component(tile, medium_crop_occupant()) - { - if !old_occupant.is_null() { - entity::despawn_recursive(old_occupant); - } + let occupant = &mut occupants[tile_idx as usize]; + + if !occupant.is_null() { + entity::despawn_recursive(*occupant); } if class.is_null() { @@ -68,15 +81,15 @@ fn main() { ) + 0.5; - let new_occupant = entity::get_all_components(class) + *occupant = entity::get_all_components(class) .with_default(medium_crop()) .with(position(), occupant_position) .with(in_chunk(), chunk) .with(chunk_tile_index(), tile_idx) .spawn(); - - entity::add_component(tile, medium_crop_occupant(), new_occupant); } + + entity::set_component(chunk, medium_crop_occupants(), occupants); }); } }); @@ -102,10 +115,12 @@ fn main() { } }); - despawn_query(medium_crop_occupant()).bind(move |entities| { - for (_, occupant) in entities { - if !occupant.is_null() { - entity::despawn_recursive(occupant); + despawn_query(medium_crop_occupants()).bind(move |entities| { + for (_, occupants) in entities { + for occupant in occupants { + if !occupant.is_null() { + entity::despawn_recursive(occupant); + } } } }); diff --git a/src/crops/server.rs b/src/crops/server.rs index 4f622a2..ed115f3 100644 --- a/src/crops/server.rs +++ b/src/crops/server.rs @@ -5,11 +5,12 @@ use ambient_api::{components::core::player::user_id, prelude::*}; use components::{ crops::*, map::{ - chunk, chunk_tile_index, chunk_tile_refs, east_neighbor, in_chunk, north_neighbor, - south_neighbor, west_neighbor, + chunk, chunk_tile_index, east_neighbor, in_chunk, north_neighbor, south_neighbor, + west_neighbor, }, }; +use flowerpot::CHUNK_SIZE; use messages::{OnPlayerLoadChunk, UpdateMediumCrops}; use crate::components::map::players_observing; @@ -19,22 +20,22 @@ mod shared; #[main] fn main() { OnPlayerLoadChunk::subscribe(move |_, data| { - let Some(chunk_tiles) = entity::get_component(data.chunk_entity, chunk_tile_refs()) else { return}; + let Some(occupants) = entity::get_component(data.chunk_entity, medium_crop_occupants()) else { return }; - let mut tiles = Vec::with_capacity(chunk_tiles.len()); - let mut classes = Vec::with_capacity(chunk_tiles.len()); + let mut tiles = Vec::with_capacity(occupants.len()); + let mut classes = Vec::with_capacity(occupants.len()); let mut dirty = false; - for (tile_idx, tile) in chunk_tiles.iter().enumerate() { - let occupant = match entity::get_component(*tile, medium_crop_occupant()) { - Some(occupant) if !occupant.is_null() => occupant, - _ => continue, - }; + for (tile_idx, occupant) in occupants.iter().enumerate() { + if occupant.is_null() { + continue; + } - let Some(class) = entity::get_component(occupant, class()) else { + let Some(class) = entity::get_component(*occupant, class()) else { eprintln!("crop {} has no class", occupant); continue; }; + eprintln!("loading class {} at {}", class, tile_idx); dirty = true; tiles.push(tile_idx.try_into().unwrap()); classes.push(class); @@ -44,137 +45,185 @@ fn main() { return; } + eprintln!("tiles updated: {:?}", tiles); + UpdateMediumCrops::new(data.chunk_pos, classes, tiles) .send_client_targeted_reliable(data.player_uid.clone()); }); - change_query((in_chunk(), chunk_tile_index(), medium_crop_occupant())) - .track_change(medium_crop_occupant()) - .bind(move |entities| { - type ChunkUpdate = Vec<(u8, EntityId)>; - type BatchedUpdates = HashMap; - let mut updates = BatchedUpdates::new(); - - for (_e, (chunk_ref, tile_idx, occupant)) in entities { - let class = if occupant.is_null() { EntityId::null()} else { - let Some(class) = entity::get_component(occupant, class()) else { - eprintln!("crop {} has no class", occupant); - continue; - }; + spawn_query(chunk()).bind(move |entities| { + for (e, _) in entities { + let tile_num = CHUNK_SIZE * CHUNK_SIZE; + let occupants = vec![EntityId::null(); tile_num]; + entity::add_component(e, medium_crop_occupants(), occupants.clone()); + entity::add_component(e, last_medium_crop_occupants(), occupants.clone()); + } + }); - class - }; + change_query(( + chunk(), + medium_crop_occupants(), + last_medium_crop_occupants(), + players_observing(), + )) + .track_change(medium_crop_occupants()) + .bind(move |entities| { + for (e, (chunk_pos, occupants, last, observers)) in entities { + entity::set_component(e, last_medium_crop_occupants(), occupants.clone()); - let update = (tile_idx, class); + let mut changed = Vec::<(u8, EntityId)>::new(); + for (tile_idx, (new, old)) in occupants.iter().zip(last.iter()).enumerate() { + if new != old { + let Some(new) = entity::get_component(*new, class()) else { continue }; + let Some(old) = entity::get_component(*old, class()) else { continue }; - if let Some(updates) = updates.get_mut(&chunk_ref) { - updates.push(update); - } else { - updates.insert(chunk_ref, vec![update]); + if new != old { + changed.push((tile_idx.try_into().unwrap(), new)); + } } } - for (chunk_ref, update) in updates { - let Some(chunk_pos) = entity::get_component(chunk_ref, chunk()) else { continue }; - let Some(observers) = entity::get_component(chunk_ref, players_observing()) else { continue }; - let (tiles, classes): (Vec<_>, Vec<_>) = update.into_iter().unzip(); - - for observer in observers { - let Some(uid) = entity::get_component(observer, user_id()) else { continue }; - UpdateMediumCrops::new(chunk_pos, classes.clone(), tiles.clone()).send_client_targeted_reliable(uid); - } + if changed.is_empty() { + continue; } - }); - spawn_query((medium_crop(), class(), on_tile())).bind(move |entities| { - for (e, (_medium, crop_class, tile)) in entities { - if let Some(old_occupant) = entity::get_component(tile, medium_crop_occupant()) { - if !old_occupant.is_null() && old_occupant != e { - entity::despawn_recursive(old_occupant); - } + eprintln!("{:#?}", changed); + + let (tiles, classes): (Vec<_>, Vec<_>) = changed.into_iter().unzip(); + for observer in observers { + let Some(uid) = entity::get_component(observer, user_id()) else { continue }; + UpdateMediumCrops::new(chunk_pos, classes.clone(), tiles.clone()) + .send_client_targeted_reliable(uid); } + } + }); + spawn_query((medium_crop(), class(), in_chunk(), chunk_tile_index())).bind(move |entities| { + type ChunkUpdate = Vec<(u8, EntityId)>; + type BatchedUpdates = HashMap; + let mut updates = BatchedUpdates::new(); + + for (e, (_medium, crop_class, chunk_ref, tile_idx)) in entities { entity::add_components( e, entity::get_all_components(crop_class) .with(age(), 0) - .with(class(), crop_class) - .with(on_tile(), tile), + .with_merge(entity::get_all_components(e)), ); - entity::add_component(tile, medium_crop_occupant(), e); + let update = (tile_idx, e); + if let Some(updates) = updates.get_mut(&chunk_ref) { + updates.push(update); + } else { + updates.insert(chunk_ref, vec![update]); + } + } + + for (chunk_ref, update) in updates { + eprintln!("update: {:#?}", update); + entity::mutate_component(chunk_ref, medium_crop_occupants(), |occupants| { + for (tile_idx, new_crop) in update { + occupants[tile_idx as usize] = new_crop; + } + }); } }); - despawn_query((medium_crop(), class(), on_tile())).bind(move |entities| { - for (e, (_medium, _class, tile)) in entities { - if entity::get_component(tile, medium_crop_occupant()) == Some(e) { - entity::set_component(tile, medium_crop_occupant(), EntityId::null()); + despawn_query((medium_crop(), class(), in_chunk(), chunk_tile_index())).bind(move |entities| { + type ChunkUpdate = Vec<(u8, EntityId)>; + type BatchedUpdates = HashMap; + let mut updates = BatchedUpdates::new(); + + for (e, (_medium, _crop_class, chunk_ref, tile_idx)) in entities { + let update = (tile_idx, e); + if let Some(updates) = updates.get_mut(&chunk_ref) { + updates.push(update); + } else { + updates.insert(chunk_ref, vec![update]); } } + + for (chunk_ref, update) in updates { + entity::mutate_component(chunk_ref, medium_crop_occupants(), |occupants| { + for (tile_idx, despawned) in update { + let occupant = &mut occupants[tile_idx as usize]; + if *occupant == despawned { + *occupant = EntityId::null(); + } + } + }); + } }); run_async(async move { - let all_crops = query((medium_crop(), on_tile(), age())).build(); + let all_crops = query((medium_crop(), in_chunk(), age())).build(); loop { sleep(0.1).await; - for (e, (_medium, _tile, old_age)) in all_crops.evaluate() { + for (e, (_medium, _chunk_ref, old_age)) in all_crops.evaluate() { let new_age = old_age + 1; entity::set_component(e, age(), new_age); } } }); - change_query((medium_crop(), on_tile(), age(), seeding_interval(), seed())) - .track_change(age()) - .bind(move |entities| { - for (_e, (_, tile, age, interval, seed)) in entities { - if age == 0 || age % interval != 0 { - continue; - } + /*change_query(( + medium_crop(), + in_chunk(), + chunk_tile_index(), + age(), + seeding_interval(), + seed(), + )) + .track_change(age()) + .bind(move |entities| { + for (_e, (_, chunk_ref, tile_idx, age, interval, seed)) in entities { + if age == 0 || age % interval != 0 { + continue; + } - let mut neighbors = [ - north_neighbor(), - east_neighbor(), - south_neighbor(), - west_neighbor(), - ]; + let mut neighbors = [ + north_neighbor(), + east_neighbor(), + south_neighbor(), + west_neighbor(), + ]; - let mut rng = thread_rng(); - neighbors.shuffle(&mut rng); + let mut rng = thread_rng(); + neighbors.shuffle(&mut rng); - for neighbor in neighbors { - let Some(neighbor) = entity::get_component(tile, neighbor) else { continue }; + for neighbor in neighbors { + let Some(neighbor) = entity::get_component(tile, neighbor) else { continue }; - if !entity::get_component(neighbor, medium_crop_occupant()) - .unwrap_or_default() - .is_null() - { - continue; - } + if !entity::get_component(neighbor, medium_crop_occupant()) + .unwrap_or_default() + .is_null() + { + continue; + } - Entity::new() - .with_default(medium_crop()) - .with(class(), seed) - .with(on_tile(), neighbor) - .spawn(); + Entity::new() + .with_default(medium_crop()) + .with(class(), seed) + .with(on_tile(), neighbor) + .spawn(); - break; - } + break; } - }); + } + });*/ change_query(( medium_crop(), - on_tile(), + in_chunk(), + chunk_tile_index(), age(), next_growth_age(), next_growth_stage(), )) .track_change(age()) .bind(move |entities| { - for (e, (_, tile, current_age, next_age, next)) in entities { + for (e, (_, chunk_ref, tile_idx, current_age, next_age, next)) in entities { if current_age < next_age { continue; } @@ -184,8 +233,9 @@ fn main() { if !next.is_null() { Entity::new() .with_default(medium_crop()) + .with(in_chunk(), chunk_ref) + .with(chunk_tile_index(), tile_idx) .with(class(), next) - .with(on_tile(), tile) .spawn(); } } diff --git a/src/game/server.rs b/src/game/server.rs index be22cd2..826ce43 100644 --- a/src/game/server.rs +++ b/src/game/server.rs @@ -1,13 +1,18 @@ use std::sync::atomic::AtomicBool; -use ambient_api::{components::core::app::name, once_cell::sync::OnceCell, prelude::*}; +use ambient_api::{once_cell::sync::OnceCell, prelude::*}; use components::{ - crops::{age, class, medium_crop, medium_crop_occupant, on_tile}, + crops::{class, medium_crop}, items::held_ref, - map::{chunk, chunk_tile_refs}, + map::chunk, player::{left_hand_ref, right_hand_ref}, }; +use crate::components::{ + crops::medium_crop_occupants, + map::{chunk_tile_index, in_chunk}, +}; + mod shared; /// A single-instance, lazily-spawned entity for use with the Prototype pattern. @@ -183,24 +188,23 @@ pub mod items { #[main] fn main() { - spawn_query((chunk(), chunk_tile_refs())).bind(move |entities| { - for (_, (chunk, tiles)) in entities { - if chunk != IVec2::ZERO { - continue; + spawn_query(chunk()) + .requires(medium_crop_occupants()) + .bind(move |entities| { + for (e, chunk) in entities { + if chunk != IVec2::ZERO { + continue; + } + + let crop_class = crops::beans::YOUNG_0.get(); + Entity::new() + .with_default(medium_crop()) + .with(class(), crop_class) + .with(in_chunk(), e) + .with(chunk_tile_index(), 0) + .spawn(); } - - let tile = tiles[0]; - - let crop_class = crops::beans::YOUNG_0.get(); - let dummy_crop = Entity::new() - .with_default(medium_crop()) - .with(class(), crop_class) - .with(on_tile(), tile) - .spawn(); - - entity::add_component(tile, medium_crop_occupant(), dummy_crop); - } - }); + }); spawn_query((left_hand_ref(), right_hand_ref())).bind(move |entities| { for (_player, (left, right)) in entities { diff --git a/src/map/ambient.toml b/src/map/ambient.toml index d5b0699..b2447ce 100644 --- a/src/map/ambient.toml +++ b/src/map/ambient.toml @@ -16,23 +16,6 @@ attributes = ["Debuggable"] type = "Ivec2" description = "A component given to all chunk entities. Contains its own chunk position." -[components."map::chunk_tile_refs"] -name = "ChunkTileRefs" -type = { type = "Vec", element_type = "EntityId" } -attributes = ["Debuggable"] -description = """ -A list of all the tile entities that are part of this tile. - -The tiles are ordered X- to X+, then Y- to Y+. For reference: -``` - X-----> -Y 0 1 2 -| 3 4 5 -| 6 7 8 -V -``` -""" - [components."map::players_observing"] name = "PlayersObserving" type = { type = "Vec", element_type = "EntityId" } @@ -63,7 +46,7 @@ description = "A reference to the chunk that this entity is in. Automatically up name = "ChunkTileIndex" attributes = ["Debuggable"] type = "U8" -description = "The index of this tile in the parent chunk's tile list." +description = "The index of the tile that this entity occupies in the parent chunk's tile list." [components."map::north_neighbor"] name = "EntityId" diff --git a/src/map/client.rs b/src/map/client.rs index e52e2ca..ca73a8c 100644 --- a/src/map/client.rs +++ b/src/map/client.rs @@ -1,7 +1,6 @@ use ambient_api::prelude::*; use components::map::*; -use flowerpot::CHUNK_SIZE; use messages::{LoadChunk, UnloadChunk}; mod shared; @@ -12,31 +11,14 @@ pub fn main() { LoadChunk::subscribe(move |_, data| { println!("Loading chunk: {}", data.pos); - - let mut tiles = Vec::with_capacity(CHUNK_SIZE * CHUNK_SIZE); - for _y in 0..CHUNK_SIZE { - for _x in 0..CHUNK_SIZE { - tiles.push(Entity::new().spawn()); - } - } - - Entity::new() - .with(chunk(), data.pos) - .with(chunk_tile_refs(), tiles) - .spawn(); + Entity::new().with(chunk(), data.pos).spawn(); }); UnloadChunk::subscribe({ let chunks = chunks.clone(); move |_, data| { println!("Unloading chunk: {}", data.pos); - let Some(chunk) = chunks.write().unwrap().remove(&data.pos) else { return }; - - for tile in entity::get_component(chunk, chunk_tile_refs()).unwrap_or_default() { - entity::despawn_recursive(tile); - } - entity::despawn_recursive(chunk); } }); diff --git a/src/map/server.rs b/src/map/server.rs index fc2b8e6..68b6876 100644 --- a/src/map/server.rs +++ b/src/map/server.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use ambient_api::prelude::*; -use flowerpot::CHUNK_SIZE; use components::map::*; use messages::{ @@ -33,7 +32,6 @@ pub fn main() { shared::init_shared_map(); let mut chunks = HashMap::new(); - let mut tiles = HashMap::new(); for x in -8..=8 { for y in -8..=8 { let position = IVec2::new(x, y); @@ -42,32 +40,12 @@ pub fn main() { .with(players_observing(), vec![]) .spawn(); - let tile_num = CHUNK_SIZE * CHUNK_SIZE; - let mut chunk_tiles = Vec::with_capacity(tile_num); - let mut tile_idx = 0; - for ty in 0..CHUNK_SIZE { - for tx in 0..CHUNK_SIZE { - let tile = Entity::new() - .with(in_chunk(), chunk) - .with(chunk_tile_index(), tile_idx) - .spawn(); - - chunk_tiles.push(tile); - let position = position * CHUNK_SIZE as i32 + ivec2(tx as i32, ty as i32); - tiles.insert(position, tile); - tile_idx += 1; - } - } - - entity::add_component(chunk, chunk_tile_refs(), chunk_tiles); - chunks.insert(position, chunk); } } // TODO faster stitching inside of the loop stitch_neighbors(chunks); - stitch_neighbors(tiles); LoadPlayerChunk::subscribe(move |_, data| { entity::mutate_component(data.chunk_entity, players_observing(), |observing| { diff --git a/src/terrain/shared.rs b/src/terrain/shared.rs index e12d5e1..00a5868 100644 --- a/src/terrain/shared.rs +++ b/src/terrain/shared.rs @@ -78,4 +78,13 @@ pub fn init_shared_terrain() { entity::add_component(e, height(), new_height); } }); + + init_spatial_queries(); +} + +fn init_spatial_queries() { + use rapier3d::prelude::*; + + let mut bodies = RigidBodySet::new(); + let mut colliders = ColliderSet::new(); }