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}