1use crate::{
2 ApplicationSemanticModel, ComponentInstanceId, ComponentInstanceStatus, ComponentInvocationId,
3 ContextSourceInstanceId, ExecutionBoundary, InstanceContextValueSlotId,
4 OptimizedComponentIrReport, SemanticId, SlotBindingId, SlotContentFragmentId, SlotId,
5 SlotOutletId, SourceProvenance,
6};
7
8pub const RUNTIME_COMPONENT_REGISTRY_SCHEMA_CONTRACT_VERSION: u32 = 1;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct RuntimeComponentRegistry {
15 pub schema_contract_version: u32,
16 pub component_definitions: Vec<RuntimeComponentDefinitionRecord>,
17 pub instances: Vec<RuntimeComponentInstanceRecord>,
18 pub slot_bindings: Vec<RuntimeComponentSlotBindingRecord>,
19 pub instance_context_bindings: Vec<RuntimeComponentContextBindingRecord>,
20 pub initialization_batches: Vec<RuntimeComponentInitializationBatch>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct RuntimeComponentDefinitionRecord {
25 pub component: SemanticId,
26 pub template: SemanticId,
27 pub declared_slots: Vec<SlotId>,
28 pub boundary: ExecutionBoundary,
29 pub provenance: SourceProvenance,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct RuntimeComponentInstanceRecord {
34 pub instance: ComponentInstanceId,
35 pub component: SemanticId,
36 pub invocation: Option<ComponentInvocationId>,
37 pub parent: Option<ComponentInstanceId>,
38 pub depth: usize,
39 pub initialization_batch: usize,
40 pub instance_cache_prefix: String,
41 pub instance_context_prefix: String,
42 pub optimized_instruction_indices: Vec<usize>,
43 pub structural_region: Option<crate::ComponentStructuralRegionId>,
44 pub provenance: SourceProvenance,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct RuntimeComponentSlotBindingRecord {
49 pub binding: SlotBindingId,
50 pub caller_instance: ComponentInstanceId,
51 pub callee_instance: ComponentInstanceId,
52 pub slot: SlotId,
53 pub outlet: SlotOutletId,
54 pub fragment: SlotContentFragmentId,
55 pub content_owner_instance: ComponentInstanceId,
56 pub provenance: SourceProvenance,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct RuntimeComponentContextBindingRecord {
61 pub consumer_instance: crate::ConsumerInstanceId,
62 pub selected_source: ContextSourceInstanceId,
63 pub provider_source: Option<crate::ProviderInstanceId>,
64 pub runtime_slot: InstanceContextValueSlotId,
65 pub provenance: SourceProvenance,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct RuntimeComponentInitializationBatch {
70 pub index: usize,
71 pub instances: Vec<ComponentInstanceId>,
72 pub context_sources: Vec<ContextSourceInstanceId>,
73 pub slot_bindings: Vec<SlotBindingId>,
74}
75
76#[must_use]
79#[allow(clippy::too_many_lines)]
80pub fn build_runtime_component_registry(
81 model: &ApplicationSemanticModel,
82 optimized: &OptimizedComponentIrReport,
83) -> RuntimeComponentRegistry {
84 let executable = model
85 .component_initialization
86 .instance_batches
87 .iter()
88 .enumerate()
89 .flat_map(|(batch, item)| item.instances.iter().cloned().map(move |id| (id, batch)))
90 .collect::<std::collections::BTreeMap<_, _>>();
91 let component_ids = executable
92 .keys()
93 .filter_map(|id| model.component_instance_plan.instances.get(id))
94 .map(|instance| instance.component.clone())
95 .collect::<std::collections::BTreeSet<_>>();
96 let component_definitions = component_ids
97 .iter()
98 .filter_map(|component| {
99 let provenance = model.provenance(component)?.clone();
100 Some(RuntimeComponentDefinitionRecord {
101 component: component.clone(),
102 template: component.template(),
103 declared_slots: model
104 .slots
105 .values()
106 .filter(|slot| slot.owner == *component)
107 .map(|slot| slot.id.clone())
108 .collect(),
109 boundary: ExecutionBoundary::Client,
110 provenance,
111 })
112 })
113 .collect();
114 let instances = executable
115 .iter()
116 .filter_map(|(id, batch)| instance_record(model, optimized, id, *batch))
117 .collect();
118 let slot_bindings = model
119 .slot_bindings
120 .bindings
121 .values()
122 .filter(|binding| executable.contains_key(&binding.callee_instance))
123 .filter_map(|binding| {
124 Some(RuntimeComponentSlotBindingRecord {
125 binding: binding.id.clone(),
126 caller_instance: binding.caller_instance.clone(),
127 callee_instance: binding.callee_instance.clone(),
128 slot: binding.slot.clone()?,
129 outlet: binding.outlet.clone()?,
130 fragment: binding.content_fragment.clone()?,
131 content_owner_instance: binding.content_owner_instance.clone(),
132 provenance: binding.provenance.clone(),
133 })
134 })
135 .collect();
136 let instance_context_bindings = model
137 .instance_context
138 .resolutions
139 .values()
140 .filter(|resolution| {
141 executable.contains_key(&resolution.consumer_instance.component_instance)
142 && model
143 .composition_types
144 .instance_context_bindings
145 .get(&resolution.consumer_instance)
146 .is_some_and(|record| {
147 record.overall == crate::CompositionCompatibility::Compatible
148 })
149 })
150 .filter_map(|resolution| {
151 Some(RuntimeComponentContextBindingRecord {
152 consumer_instance: resolution.consumer_instance.clone(),
153 selected_source: resolution.selected_source.clone()?,
154 provider_source: resolution.provider_instance.clone(),
155 runtime_slot: resolution.value_slot.clone()?,
156 provenance: resolution.provenance.clone(),
157 })
158 })
159 .collect();
160 let initialization_batches = model
161 .component_initialization
162 .instance_batches
163 .iter()
164 .map(|batch| RuntimeComponentInitializationBatch {
165 index: batch.index,
166 instances: batch.instances.clone(),
167 context_sources: batch.context_sources.clone(),
168 slot_bindings: model
169 .component_initialization
170 .slot_binding_batches
171 .iter()
172 .filter(|bindings| bindings.index == batch.index)
173 .flat_map(|bindings| bindings.bindings.clone())
174 .collect(),
175 })
176 .collect();
177 RuntimeComponentRegistry {
178 schema_contract_version: RUNTIME_COMPONENT_REGISTRY_SCHEMA_CONTRACT_VERSION,
179 component_definitions,
180 instances,
181 slot_bindings,
182 instance_context_bindings,
183 initialization_batches,
184 }
185}
186
187fn instance_record(
188 model: &ApplicationSemanticModel,
189 optimized: &OptimizedComponentIrReport,
190 id: &ComponentInstanceId,
191 initialization_batch: usize,
192) -> Option<RuntimeComponentInstanceRecord> {
193 let instance = model.component_instance_plan.instances.get(id)?;
194 (instance.status == ComponentInstanceStatus::Planned).then_some(RuntimeComponentInstanceRecord {
195 instance: instance.id.clone(),
196 component: instance.component.clone(),
197 invocation: instance.invocation.clone(),
198 parent: instance.parent_instance.clone(),
199 depth: instance.depth,
200 initialization_batch,
201 instance_cache_prefix: format!("component-cache:{}", instance.id),
202 instance_context_prefix: format!("component-context:{}", instance.id),
203 optimized_instruction_indices: optimized
204 .optimized_report
205 .instructions
206 .iter()
207 .filter(|instruction| matches!(
208 &instruction.operation,
209 crate::ComponentIrOperation::CreateComponentInstance { instance: operation_instance, .. }
210 | crate::ComponentIrOperation::InitializeComponentInstance { instance: operation_instance, .. }
211 | crate::ComponentIrOperation::MaterializeComponentTemplate { instance: operation_instance, .. }
212 if operation_instance == &instance.id
213 ))
214 .map(|instruction| instruction.index)
215 .collect(),
216 structural_region: instance.structural_region.clone(),
217 provenance: instance.provenance.clone(),
218 })
219}
220
221#[cfg(test)]
222mod tests {
223 use crate::{
224 build_application_semantic_model, build_runtime_component_registry,
225 RUNTIME_COMPONENT_REGISTRY_SCHEMA_CONTRACT_VERSION,
226 };
227
228 #[test]
229 fn projects_only_planned_instances_in_deterministic_id_order() {
230 let model = build_application_semantic_model(&presolve_parser::parse_file(
231 "src/Registry.tsx",
232 r#"
233@component("x-leaf") class Leaf extends Component { render() { return <i />; } }
234@component("x-page") class Page extends Component { render() { return <><Leaf /><Leaf /></>; } }
235"#,
236 ));
237 let registry = build_runtime_component_registry(&model, &model.component_ir_optimization);
238 assert_eq!(
239 registry.schema_contract_version,
240 RUNTIME_COMPONENT_REGISTRY_SCHEMA_CONTRACT_VERSION
241 );
242 assert_eq!(registry.instances.len(), 3);
243 assert_ne!(
244 registry.instances[1].instance,
245 registry.instances[2].instance
246 );
247 assert!(registry
248 .instances
249 .windows(2)
250 .all(|pair| pair[0].instance < pair[1].instance));
251 assert!(registry
252 .instances
253 .iter()
254 .all(|record| !record.optimized_instruction_indices.is_empty()));
255 }
256}