implements PLN-0019
This commit is contained in:
parent
5a0476e8b0
commit
3931e86b41
@ -22,6 +22,7 @@ const EMPTY_SPRITE: Sprite = Sprite {
|
||||
pub enum SceneStatus {
|
||||
#[default]
|
||||
Unbound,
|
||||
Available { scene_bank_id: usize },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -177,6 +178,35 @@ impl FrameComposer {
|
||||
(self.camera_x_px, self.camera_y_px)
|
||||
}
|
||||
|
||||
pub fn bind_scene(&mut self, scene_bank_id: usize) -> bool {
|
||||
let Some(scene) = self.scene_bank_pool.scene_bank_slot(scene_bank_id) else {
|
||||
self.unbind_scene();
|
||||
return false;
|
||||
};
|
||||
|
||||
let (cache, resolver) =
|
||||
Self::build_scene_runtime(self.viewport_width_px, self.viewport_height_px, &scene);
|
||||
self.active_scene_id = Some(scene_bank_id);
|
||||
self.active_scene = Some(scene);
|
||||
self.scene_status = SceneStatus::Available { scene_bank_id };
|
||||
self.cache = Some(cache);
|
||||
self.resolver = Some(resolver);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn unbind_scene(&mut self) {
|
||||
self.active_scene_id = None;
|
||||
self.active_scene = None;
|
||||
self.scene_status = SceneStatus::Unbound;
|
||||
self.cache = None;
|
||||
self.resolver = None;
|
||||
}
|
||||
|
||||
pub fn set_camera(&mut self, x: i32, y: i32) {
|
||||
self.camera_x_px = x;
|
||||
self.camera_y_px = y;
|
||||
}
|
||||
|
||||
pub fn cache(&self) -> Option<&SceneViewportCache> {
|
||||
self.cache.as_ref()
|
||||
}
|
||||
@ -204,6 +234,31 @@ impl FrameComposer {
|
||||
pub fn ordered_sprites(&self) -> Vec<Sprite> {
|
||||
self.sprite_controller.ordered_sprites()
|
||||
}
|
||||
|
||||
fn build_scene_runtime(
|
||||
viewport_width_px: usize,
|
||||
viewport_height_px: usize,
|
||||
scene: &SceneBank,
|
||||
) -> (SceneViewportCache, SceneViewportResolver) {
|
||||
let min_tile_px =
|
||||
scene.layers.iter().map(|layer| layer.tile_size as usize).min().unwrap_or(8);
|
||||
let cache_width_tiles = viewport_width_px.div_ceil(min_tile_px) + 5;
|
||||
let cache_height_tiles = viewport_height_px.div_ceil(min_tile_px) + 4;
|
||||
let hysteresis_safe_px = min_tile_px.saturating_sub(4) as i32;
|
||||
let hysteresis_trigger_px = (min_tile_px + 4) as i32;
|
||||
|
||||
(
|
||||
SceneViewportCache::new(scene, cache_width_tiles, cache_height_tiles),
|
||||
SceneViewportResolver::new(
|
||||
viewport_width_px as i32,
|
||||
viewport_height_px as i32,
|
||||
cache_width_tiles,
|
||||
cache_height_tiles,
|
||||
hysteresis_safe_px,
|
||||
hysteresis_trigger_px,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -269,6 +324,75 @@ mod tests {
|
||||
assert_eq!(scene.layers[0].parallax_factor.y, 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_scene_stores_scene_identity_and_shared_reference() {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
banks.install_scene_bank(3, Arc::new(make_scene()));
|
||||
|
||||
let expected_scene = banks.scene_bank_slot(3).expect("scene bank slot 3 should be resident");
|
||||
let mut frame_composer = FrameComposer::new(320, 180, banks);
|
||||
|
||||
assert!(frame_composer.bind_scene(3));
|
||||
|
||||
assert_eq!(frame_composer.active_scene_id(), Some(3));
|
||||
assert!(Arc::ptr_eq(
|
||||
frame_composer.active_scene().expect("active scene should exist"),
|
||||
&expected_scene,
|
||||
));
|
||||
assert_eq!(frame_composer.scene_status(), SceneStatus::Available { scene_bank_id: 3 });
|
||||
assert!(frame_composer.cache().is_some());
|
||||
assert!(frame_composer.resolver().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_scene_clears_scene_and_cache_state() {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
banks.install_scene_bank(1, Arc::new(make_scene()));
|
||||
|
||||
let mut frame_composer = FrameComposer::new(320, 180, banks);
|
||||
assert!(frame_composer.bind_scene(1));
|
||||
|
||||
frame_composer.unbind_scene();
|
||||
|
||||
assert_eq!(frame_composer.active_scene_id(), None);
|
||||
assert!(frame_composer.active_scene().is_none());
|
||||
assert_eq!(frame_composer.scene_status(), SceneStatus::Unbound);
|
||||
assert!(frame_composer.cache().is_none());
|
||||
assert!(frame_composer.resolver().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_camera_stores_top_left_pixel_coordinates() {
|
||||
let mut frame_composer = FrameComposer::new(320, 180, Arc::new(MemoryBanks::new()));
|
||||
|
||||
frame_composer.set_camera(-12, 48);
|
||||
|
||||
assert_eq!(frame_composer.camera(), (-12, 48));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_scene_derives_cache_and_resolver_from_eight_pixel_layers() {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
banks.install_scene_bank(0, Arc::new(make_scene()));
|
||||
|
||||
let mut frame_composer = FrameComposer::new(320, 180, banks);
|
||||
assert!(frame_composer.bind_scene(0));
|
||||
|
||||
let cache = frame_composer.cache().expect("cache should exist for bound scene");
|
||||
assert_eq!((cache.width(), cache.height()), (45, 27));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_scene_binding_falls_back_to_no_scene_state() {
|
||||
let mut frame_composer = FrameComposer::new(320, 180, Arc::new(MemoryBanks::new()));
|
||||
|
||||
assert!(!frame_composer.bind_scene(7));
|
||||
|
||||
assert_eq!(frame_composer.scene_status(), SceneStatus::Unbound);
|
||||
assert!(frame_composer.cache().is_none());
|
||||
assert!(frame_composer.resolver().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sprite_controller_begin_frame_resets_sprite_count_and_buckets() {
|
||||
let mut controller = SpriteController::new();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user