mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-11 17:19:31 -10:00
test: cross parallel tick helper thresholds with real resources
Compare deferred and in-place metadata for 2,085 varied mobs, including pack-selected effect particles and switch-off vector reuse. Exercise live climbing, snapshot order and nearest-player invalidation for 293 mobs. Build 132 random-tick section masks ahead and retain exact event, state and random comparisons through edits and reloads.
Co-Authored-By: Kacper <[email protected]>
Co-Authored-By: GPT 6.1 Sol <[email protected]>
(cherry picked from commit 41a579d428)
This commit is contained in:
1 parent
0b21fcbf60
commit
5cc88b517b
3 files changed
+185
-10
No files matched your search
@@ -165,3 +165,83 @@ fn hookless<'w>(base: &MobWorld<'w>) -> impl Fn() -> MobWorld<'w> + Sync + use<'
|
||||
border,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::super::natural_spawning::LevelPlayer;
|
||||
use super::*;
|
||||
use horizon_core::geometry::Aabb;
|
||||
|
||||
#[test]
|
||||
#[ignore = "needs prepared resources and scratch homes/cwd"]
|
||||
fn preparation_refreshes_climbing_and_nearest_after_a_move() {
|
||||
let mut game = super::super::super::native_machines::tests::server_game();
|
||||
game.build_registries();
|
||||
game.set_allow_commands(true);
|
||||
for command in [
|
||||
"setblock 8 80 9 stone",
|
||||
"setblock 8 80 8 ladder[facing=north]",
|
||||
"summon pig 8.5 80 8.5 {NoAI:1b}",
|
||||
"summon pig 9.5 80 8.5 {NoAI:1b}",
|
||||
] {
|
||||
assert!(game.run_command(command), "{command}");
|
||||
}
|
||||
assert_eq!(game.mobs.len(), 2);
|
||||
let examples = game.mobs.clone();
|
||||
game.mobs = (0..MIN_MOBS + 37)
|
||||
.map(|i| {
|
||||
let mut mob = examples[i % 2].clone();
|
||||
mob.id = 10_000 + i as u32;
|
||||
mob.on_climbable = i % 2 != 0; // deliberately stale in both cases
|
||||
mob
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
let mut snapshot = Vec::new();
|
||||
game.prepare_mobs(None, &mut snapshot);
|
||||
assert_eq!(snapshot.len(), game.mobs.len());
|
||||
for (i, (m, seen)) in game.mobs.iter().zip(&snapshot).enumerate() {
|
||||
assert_eq!(m.on_climbable, i % 2 == 0, "mob {i}");
|
||||
assert_eq!(seen.id, m.id);
|
||||
assert_eq!(seen.position, m.position);
|
||||
assert_eq!(seen.pushable, i % 2 != 0, "snapshot {i}");
|
||||
}
|
||||
|
||||
let player = |x, spectator| {
|
||||
let position = Vec3::new(x, 80.0, 8.5);
|
||||
LevelPlayer {
|
||||
uuid: 0,
|
||||
position,
|
||||
bounds: Aabb::new(position, position),
|
||||
alive: false, // dead players still count for checkDespawn
|
||||
creative: false,
|
||||
spectator,
|
||||
}
|
||||
};
|
||||
let players = [player(8.5, true), player(7.5, false), player(9.5, false)];
|
||||
let index = NearestPlayers::new(&players);
|
||||
let nearest = MobsNearest::new(&index, &game.mobs, true);
|
||||
for (i, mob) in game.mobs.iter().enumerate() {
|
||||
let expected = if i % 2 == 0 {
|
||||
players[1].position
|
||||
} else {
|
||||
players[2].position
|
||||
};
|
||||
assert_eq!(nearest.at(i, mob.position), Some(expected));
|
||||
// A previous mob's push moved this mob across the nearest-player tie.
|
||||
assert_eq!(
|
||||
nearest.at(i, Vec3::new(10.0, 80.0, 8.5)),
|
||||
Some(players[2].position)
|
||||
);
|
||||
}
|
||||
let no_cache = MobsNearest::new(&index, &game.mobs, false);
|
||||
assert_eq!(
|
||||
no_cache.at(0, game.mobs[0].position),
|
||||
Some(players[1].position)
|
||||
);
|
||||
let spectators = [player(8.5, true)];
|
||||
let index = NearestPlayers::new(&spectators);
|
||||
let nearest = MobsNearest::new(&index, &game.mobs, true);
|
||||
assert_eq!(nearest.at(0, game.mobs[0].position), None);
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,13 @@ mod tests {
|
||||
#[test]
|
||||
#[ignore = "needs prepared resources and scratch homes/cwd"]
|
||||
fn deferred_states_match_those_built_in_place() {
|
||||
crate::mod_api::in_own_process(
|
||||
"game::lan::mob_states::tests::deferred_states_match_those_built_in_place",
|
||||
deferred_states_match_those_built_in_place_in_own_process,
|
||||
);
|
||||
}
|
||||
|
||||
fn deferred_states_match_those_built_in_place_in_own_process() {
|
||||
let mut game = super::super::super::native_machines::tests::server_game();
|
||||
game.build_registries();
|
||||
game.set_allow_commands(true);
|
||||
@@ -161,7 +168,16 @@ mod tests {
|
||||
] {
|
||||
assert!(game.run_command(command), "{command}");
|
||||
}
|
||||
assert!(game.mobs.len() >= 7);
|
||||
let examples = game.mobs.clone();
|
||||
game.mobs = (0..MIN_MOBS * 2 + 37)
|
||||
.map(|i| {
|
||||
let mut mob = examples[i % examples.len()].clone();
|
||||
mob.id = 10_000 + i as u32;
|
||||
mob.removed = i % 11 == 0;
|
||||
mob
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
game.lan_host(
|
||||
super::super::LanSettings {
|
||||
port: 0,
|
||||
@@ -186,17 +202,85 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
let mut lan = game.lan.take().unwrap();
|
||||
// The shared tables must retain pack-selected effect colors, not just
|
||||
// vanilla's registry color. This process owns the global pack palette.
|
||||
horizon_client::brewing::set_pack_colors(vec![("speed".into(), 0xAA12_3456)]);
|
||||
let players = Default::default();
|
||||
let mut runs = Vec::new();
|
||||
for defer in [false, true] {
|
||||
DEFER.with(|d| d.set(Some(defer)));
|
||||
struct Reset;
|
||||
impl Drop for Reset {
|
||||
fn drop(&mut self) {
|
||||
DEFER.with(|d| d.set(None));
|
||||
}
|
||||
}
|
||||
let _reset = Reset;
|
||||
for after in [0, 10_012] {
|
||||
runs.clear();
|
||||
for defer in [false, true, false] {
|
||||
DEFER.with(|d| d.set(Some(defer)));
|
||||
let mut states = States::default();
|
||||
game.lan_entities(&players, &mut lan.hub, &mut states, false, after, None);
|
||||
runs.push(states.as_slice().to_vec());
|
||||
}
|
||||
let expected_ids: Vec<_> = game
|
||||
.mobs
|
||||
.iter()
|
||||
.filter(|m| !m.removed && m.id > after)
|
||||
.map(|m| m.id as i32)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
runs[0].iter().map(|e| e.id).collect::<Vec<_>>(),
|
||||
expected_ids
|
||||
);
|
||||
assert!(runs[0].iter().filter(|e| !e.meta.is_empty()).count() >= MIN_MOBS);
|
||||
assert!(runs[0].iter().any(|e| !e.attributes.is_empty()));
|
||||
let colored: Vec<_> = runs[0]
|
||||
.iter()
|
||||
.filter(|e| e.kind == "minecraft:piglin")
|
||||
.collect();
|
||||
assert!(!colored.is_empty());
|
||||
for e in colored {
|
||||
let particles = e.meta.iter().find(|e| e.0 == 10).unwrap();
|
||||
let Value::Particles(particles) = &particles.2 else {
|
||||
panic!("effect particle metadata");
|
||||
};
|
||||
assert_eq!(particles.len(), 1);
|
||||
assert_eq!(
|
||||
particles[0].data,
|
||||
Some(horizon_protocol::entity_data::ParticleData::Color(
|
||||
0xFF12_3456_u32 as i32
|
||||
))
|
||||
);
|
||||
}
|
||||
assert_eq!(runs[0], runs[1], "deferred, after {after}");
|
||||
assert_eq!(runs[0], runs[2], "switch off, after {after}");
|
||||
|
||||
// With deferral off the caller can read the metadata immediately,
|
||||
// before fill, and fill must not replace the vectors it already owns.
|
||||
let m = game
|
||||
.mobs
|
||||
.iter()
|
||||
.find(|m| !m.removed && m.id > after)
|
||||
.unwrap();
|
||||
let mut states = States::default();
|
||||
game.lan_entities(&players, &mut lan.hub, &mut states, false, 0, None);
|
||||
runs.push(states.as_slice().to_vec());
|
||||
let e = states.add(m.id as i32, 0, &m.kind.id);
|
||||
let mut later = Later::new(game.world.game_time);
|
||||
later.state((0, 0), m, None, lan.hub.sync_data(), game.attr_sources(), e);
|
||||
assert_eq!(e.meta, runs[0][0].meta);
|
||||
assert_eq!(e.defaults, runs[0][0].defaults);
|
||||
assert_eq!(e.attributes, runs[0][0].attributes);
|
||||
let pointers = (e.meta.as_ptr(), e.defaults.as_ptr(), e.attributes.as_ptr());
|
||||
later.fill(
|
||||
&game.mobs,
|
||||
lan.hub.sync_data(),
|
||||
game.attr_sources(),
|
||||
&mut states,
|
||||
);
|
||||
let e = &states.as_slice()[0];
|
||||
assert_eq!(
|
||||
pointers,
|
||||
(e.meta.as_ptr(), e.defaults.as_ptr(), e.attributes.as_ptr())
|
||||
);
|
||||
}
|
||||
DEFER.with(|d| d.set(None));
|
||||
assert!(runs[0].iter().filter(|e| !e.meta.is_empty()).count() >= 7);
|
||||
assert!(runs[0].iter().any(|e| !e.attributes.is_empty()));
|
||||
assert_eq!(runs[0], runs[1]);
|
||||
}
|
||||
}
|
||||
@@ -519,6 +519,11 @@ mod tick_parity {
|
||||
time: 0,
|
||||
speed: 3,
|
||||
};
|
||||
// More than 128 ticking sections: prepare_masks must exercise its
|
||||
// helper path when HORIZON_PARALLEL_TICK is enabled, not just defer work.
|
||||
for x in 2..=65 {
|
||||
original.chunks.insert((x, 0), chunk(x));
|
||||
}
|
||||
Arc::make_mut(original.chunks.get_mut(&(1, 0)).unwrap()).lit = true; // precipitation-only chunk
|
||||
let mut cached = original.clone();
|
||||
// The masked path reads only the drawn blocks that can tick.
|
||||
@@ -530,7 +535,7 @@ mod tick_parity {
|
||||
let mut cache = RandomTickSections::default();
|
||||
let mut mask_cache = RandomTickSections::default();
|
||||
let (mut a, mut b, mut c) = (Ticks::new(12345), Ticks::new(12345), Ticks::new(12345));
|
||||
for x in -1..=1 {
|
||||
for x in -1..=65 {
|
||||
a.add_empty_chunk(ChunkPos::new(x, 0)).unwrap();
|
||||
b.add_empty_chunk(ChunkPos::new(x, 0)).unwrap();
|
||||
c.add_empty_chunk(ChunkPos::new(x, 0)).unwrap();
|
||||
@@ -543,6 +548,12 @@ mod tick_parity {
|
||||
level.reads.borrow_mut().clear();
|
||||
level.events.clear();
|
||||
match step {
|
||||
4 => {
|
||||
// The large fixture has exercised every tick speed and
|
||||
// helper-mask invalidation; keep the remaining edit,
|
||||
// unload and reload cases small as before.
|
||||
level.chunks.retain(|&(x, _), _| x <= 1);
|
||||
}
|
||||
12 => {
|
||||
level.chunks.remove(&(-1, 0));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user