notes/architecture/01-CORE.md
The dioxus-core crate is the heart of Dioxus, implementing the virtual DOM, component lifecycle, rendering pipeline, and reactive runtime.
The VirtualDom struct orchestrates the entire reactive component tree:
VirtualDom
├── scopes: Slab<ScopeState> // Arena allocator for all component scopes
├── dirty_scopes: BTreeSet<ScopeOrder> // Scopes marked for re-render (height-ordered)
├── runtime: Rc<Runtime> // Shared async runtime
├── resolved_scopes: Vec<ScopeId> // Scopes resolved during suspense
└── rx: UnboundedReceiver<SchedulerMsg> // Event channel from scheduler
VirtualDom::new(app) - Create new DOM with root componentrebuild() / rebuild_in_place() - Full tree reconstructionrender_immediate() - Render all dirty scopes without suspense blockingwait_for_work() - Async poll for futures and scheduler eventswait_for_suspense() - Block until all suspended futures completemark_dirty(scope_id) - Schedule scope for re-renderwith_root_context<T>() - Inject dependency into root scoperebuild() calls run_scope(ScopeId::ROOT)LastRenderedNode and calls create_scope()create_scope() recursively creates all child scopes and DOM elementsWriteMutations sinkwait_for_work() polls scheduler and pending futuresprocess_events() consumes SchedulerMsg::Immediate(scope_id) messagesrender_immediate() pops dirty scopes in height order (top-to-bottom)run_and_diff_scope() executes component, then diff_scope()WriteMutations traitComponent<P> type alias: fn(P) -> ElementComponentFunction<P, M> trait for component functions
fn_ptr(&self) -> usize - Raw function pointer for identityrebuild(&self, props: P) -> Element - Execute componentProperties trait for all props:
type Builder - For DSL prop constructionbuilder() - Create props buildermemoize(&mut self, other: &Self) -> bool - Check if props changedinto_vcomponent() - Convert to VComponentVProps<F,P,M> wraps strongly-typed props, erased to BoxedAnyProps via AnyProps trait:
render(&self) -> Elementmemoize(&mut self, other: &dyn Any) -> boolprops() / props_mut() - Access as dyn Anyduplicate() - Clone into new boxSmall unique identifier (usize index into Slab):
ScopeId::ROOT (0) - Root wrapper scopeScopeId::ROOT_SUSPENSE_BOUNDARY (1) - Default suspenseScopeId::ROOT_ERROR_BOUNDARY (2) - Default error boundaryScopeId::APP (3) - User's root componentScopeState
├── context_id: ScopeId
├── last_rendered_node: Option<LastRenderedNode>
├── props: BoxedAnyProps
└── reactive_context: ReactiveContext
Scope
├── name: &'static str // Component name for debugging
├── id: ScopeId
├── parent_id: Option<ScopeId>
├── height: u32 // Distance from root
├── hooks: RefCell<Vec<Box<dyn Any>>> // Hook state storage
├── hook_index: Cell<usize> // Current hook being accessed
├── shared_contexts: RefCell<Vec<Box<dyn Any>>>
├── spawned_tasks: RefCell<FxHashSet<Task>>
├── before_render / after_render // Callbacks
├── status: RefCell<ScopeStatus> // Mounted/Unmounted
└── suspense_boundary: SuspenseLocation
provide_context<T: Clone + 'static>(context: T) - Store in scopeconsume_context<T>() - Retrieve from this scope or any parentparent_idpub struct Event<T: ?Sized> {
pub data: Rc<T>,
pub(crate) metadata: Rc<RefCell<EventMetadata>>,
}
pub struct EventMetadata {
pub propagates: bool,
pub prevent_default: bool,
}
Runtime::handle_event() looks up element by ElementIdElementRef from slabhandle_bubbling_event() walks parent chainstop_propagation()diff_scope() gets old/new VNodes and calls old.diff_node(new, dom, mutations)
diff_attributes() for dynamic attrsdiff_vtext() updates contentFxHashMap for old→new key matchingInterface between VirtualDOM and real DOM:
append_children(id, count) - Add N nodes to elementassign_node_id(path, id) - Mark element at template pathcreate_placeholder(id) - Create marker nodecreate_text_node(value, id) - Create text nodeload_template(template, index, id) - Clone from template cachereplace_node_with(id, count) - Replace elementset_attribute(name, ns, value, id) - Update attributecreate_event_listener(name, id) - Register listenerremove_node(id) - Delete elementpub enum Mutation {
AppendChildren { id, m },
CreatePlaceholder { id },
CreateTextNode { value, id },
LoadTemplate { index, id },
ReplaceWith { id, m },
SetAttribute { name, ns, value, id },
// ... etc
}
Composite key: (height: u32, id: ScopeId) in BTreeSet ensures parent scopes run before children.
queue_scope(order) - Add to dirty_scopesqueue_task(task, order) - Add to dirty_taskspop_work() - Get next dirty scope or taskpop_effect() - Get next pending effectManages async/scope/task coordination:
Runtime
├── scope_states: RefCell<Vec<Option<Scope>>>
├── scope_stack: RefCell<Vec<ScopeId>>
├── suspense_stack: RefCell<Vec<SuspenseLocation>>
├── tasks: RefCell<SlotMap<DefaultKey, Rc<LocalTask>>>
├── current_task: Cell<Option<Task>>
├── dirty_tasks: RefCell<BTreeSet<DirtyTasks>>
├── pending_effects: RefCell<BTreeSet<Effect>>
├── rendering: Cell<bool>
├── sender: UnboundedSender<SchedulerMsg>
├── elements: RefCell<Slab<Option<ElementRef>>>
└── mounts: RefCell<Slab<VNodeMount>>
id: TaskId (slotmap key)!Send + !Sync markerTask::new(future) - Spawn new taskcancel() - Remove taskpause() / resume() - Control pollingwake() - Wake sleeping taskPin<Box<dyn Future<Output = ()>>>SchedulerMsg::TaskNotifiedEffects run AFTER mutations are applied to DOM:
use_effect() in componentRuntime::pending_effectsfinish_render() sends SchedulerMsg::EffectQueuedpoll_tasks() pops effects and calls effect.run()suspended_tasks: RefCell<Vec<SuspendedFuture>>suspended_nodes: RefCell<Option<VNode>>frozen: Cell<bool> for server-side lockingsuspend() → Err(SuspendedFuture)Element::Err(RenderError::Suspended) propagates upSuspenseBoundary catches itpub struct VNode {
vnode: Rc<VNodeInner>,
mount: Cell<MountId>,
}
pub struct VNodeInner {
pub key: Option<String>,
pub template: Template,
pub dynamic_nodes: Box<[DynamicNode]>,
pub dynamic_attrs: Box<[Box<[Attribute]>]>,
}
pub struct Template {
pub roots: &'static [TemplateNode],
pub node_paths: &'static [&'static [u8]],
pub attr_paths: &'static [&'static [u8]],
}
Element { tag, namespace, attrs, children } - Static elementText { text } - Static textDynamic { id } - Index into dynamic_nodesComponent(VComponent) - Child componentText(VText) - Text nodePlaceholder(VPlaceholder) - Suspense/empty markerFragment(Vec<VNode>) - Multiple childrenerror: Rc<RefCell<Option<CapturedError>>>subscribers: Subscribers for listening componentsElement::Err(error)ErrorBoundary catchesErrorContext stores errorerror_context.error() availableDefault scope hierarchy:
ScopeId(0): RootScopeWrapper
└── ScopeId(1): SuspenseBoundary
└── ScopeId(2): ErrorBoundary
└── ScopeId(3): User's Root App
Provides default suspense and error handling for the entire tree.