dev/render-all-scene-cache-and-camera-integration #16

Merged
bquarkz merged 21 commits from dev/render-all-scene-cache-and-camera-integration into master 2026-04-18 16:20:50 +00:00
Showing only changes of commit 3931e86b41 - Show all commits

View File

@ -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();