khora_lanes/ecs_lane/
compaction_lane.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A lane for compacting component pages by cleaning up orphaned data.
16
17use khora_data::ecs::{PageIndex, SemanticDomain, WorldMaintenance};
18
19/// A work plan for a single frame's garbage collection pass.
20#[derive(Debug, Default)]
21pub struct GcWorkPlan {
22    /// The budget to determine how many items we can clean this frame.
23    pub budget: usize,
24    /// The list of orphaned data locations to clean up.
25    pub items_to_clean: Vec<(PageIndex, SemanticDomain)>,
26    /// The list of pages that have fragmentation (holes) to be vacuumed.
27    /// Stores `(page_index, hole_row_index)`.
28    pub pages_to_vacuum: Vec<(u32, u32)>,
29}
30
31/// The lane responsible for executing the physical cleanup of orphaned component data.
32#[derive(Debug, Default)]
33pub struct CompactionLane;
34
35impl CompactionLane {
36    /// Creates a new `CompactionLane`.
37    pub fn new() -> Self {
38        Self
39    }
40
41    /// Executes the compaction work defined in the `GcWorkPlan`.
42    ///
43    /// It requires a trait object with `WorldMaintenance` capabilities to perform
44    /// its low-level cleanup operations.
45    pub fn run(&self, world: &mut dyn WorldMaintenance, work_plan: &GcWorkPlan) {
46        // 1. Cleanup orphans
47        for (location, domain) in work_plan.items_to_clean.iter().take(work_plan.budget) {
48            world.cleanup_orphan_at(*location, *domain);
49        }
50
51        // 2. Vacuum pages (compact fragmentation)
52        for (page_id, hole_row_index) in work_plan.pages_to_vacuum.iter().take(work_plan.budget) {
53            world.vacuum_hole_at(*page_id, *hole_row_index);
54        }
55    }
56}
57
58impl khora_core::lane::Lane for CompactionLane {
59    fn strategy_name(&self) -> &'static str {
60        "Compaction"
61    }
62
63    fn lane_kind(&self) -> khora_core::lane::LaneKind {
64        khora_core::lane::LaneKind::Ecs
65    }
66
67    fn execute(
68        &self,
69        ctx: &mut khora_core::lane::LaneContext,
70    ) -> Result<(), khora_core::lane::LaneError> {
71        use khora_core::lane::{LaneError, Slot};
72
73        let world = ctx
74            .get::<Slot<dyn WorldMaintenance>>()
75            .ok_or(LaneError::missing("Slot<dyn WorldMaintenance>"))?
76            .get();
77        let work_plan = ctx
78            .get::<Slot<GcWorkPlan>>()
79            .ok_or(LaneError::missing("Slot<GcWorkPlan>"))?
80            .get_ref();
81
82        self.run(world, work_plan);
83        Ok(())
84    }
85
86    fn as_any(&self) -> &dyn std::any::Any {
87        self
88    }
89
90    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
91        self
92    }
93}