Performance & Staged Loading¶
Philosophy
Full Calendar is designed to be instant-on, even for vaults with thousands of events spread across years of history. We achieve this through a staged loading strategy, non-blocking main-thread yields, zero-allocation structural change detection, reactive vault-backed parse caching, and efficient lookahead indexing.
Staged Loading Sequence & Provider Priority Tiers¶
When the plugin initializes (or when a full resync is triggered), the ProviderRegistry executes a non-blocking, multi-tier staged fetch based on each provider's loadPriority and isRemote classification:
Load Priority Classification Rules¶
| Tier | Priority Range | Execution Characteristics | Providers |
|---|---|---|---|
| Stage 0 (Local Sync) | loadPriority < 100 |
Fetched immediately and synchronously during startup. UI populates without waiting for network I/O. | FullNoteProvider (10)DailyNoteProvider (20)TasksPluginProvider (30)TaskNotesProvider (40) |
| Stage 1 & 2 (Remote Async) | loadPriority >= 100 |
Fetched asynchronously in background waves using range filtering and yielding. | CalDAVProvider (110)GoogleProvider (120)GoogleTasksProvider (125)ICSProvider (140)OutlookProvider (150) |
Stage Breakdown¶
Stage 1 (Local): The Critical Window¶
- Range: Current Date ± 3 months (
stage1Range). - Goal: Immediate UI population from local vault notes (Time to Interactive < 250ms).
- Behavior: Local providers (
loadPriority < 100) load range-filtered events. Once complete,onAllComplete()fires so the calendar UI becomes interactive immediately.
Stage 2 (Local): Background Vault Notes¶
- Range: All time (Full Vault History).
- Goal: Searchability and long-term local event completeness.
- Behavior: Processes full local provider datasets in the background. Batched file loading (
BATCH_SIZE = 20) withyieldToMainThread()ensures zero main-thread UI stutters.
Stage 1 (Remote): Critical Window Remote Sync¶
- Range: Current Date ± 3 months.
- Goal: Fetch visible remote events without blocking UI interactions.
- Behavior: Fetches active range events for remote providers (CalDAV, Google, Google Tasks, ICS, Outlook).
Stage 2 (Remote): Background Remote Sync & Optimization¶
- Range: All time (Full Remote History).
- Goal: Complete remote calendar sync.
- Behavior: Fetches remaining remote historical data. For read-only remote providers (like ICS feeds) where Stage 1 already retrieved the full payload, Stage 2 skips redundant network re-downloads and re-parsing.
Reactive In-Memory Daily Note Parse Cache (DailyNoteParseCache)¶
To eliminate disk I/O and line-parsing bottlenecks when scanning daily notes:
- mtime + size Validation: Stores parsed [OFCEvent, EventLocation | null][] arrays in memory. On subsequent reads, if file.stat.mtime and file.stat.size match the cached entry, the provider returns the cached events in 0ms (bypassing disk read and regex parsing).
- Reactive Vault Invalidation: Binds directly to Obsidian Vault lifecycle events (vault.on('modify'), vault.on('delete'), vault.on('rename')) to clear/evict modified file entries immediately, guaranteeing zero stale data.
Zero-Allocation Structural Equality (areEventsEqual)¶
During cache delta syncs (CacheSyncHandler.syncCalendar()):
- Replaces expensive JSON.stringify() serialization with direct scalar property comparisons across all OFCEvent discriminator branches (single, recurring, rrule).
- Zero Allocations & Zero Hash Collisions: Executes in nanoseconds per event pair, generates 0 temporary string allocations, and eliminates 32-bit hash collision risks completely.
Streamlined Lookahead Window Filtering (TimeEngine)¶
TimeEngine maintains a rolling 7-day lookahead cache for notifications and status bar updates:
- $O(1)$ Fast Date String Filtering: Skips single events outside the [now - 24h, now + 7d] window using scalar date string comparisons before allocating Luxon DateTime objects or running timezone math.
- Keeps occurrence cache rebuilds bounded under < 5ms even in vaults containing thousands of historical single events.
Off-Main-Thread Web Worker Engine & 6ms Frame Budget Ceiling (WorkerManager & scheduler.yield)¶
To guarantee that background cache indexing, parsing, and sync calculations are 100% imperceptible to the user with zero main-thread UI freezing:
1. Obsidian-Compliant Single-File Inline Web Worker Engine (WorkerManager)¶
- Single-File Inline Blob Worker Architecture:
- Web Workers are instantiated directly from in-memory JavaScript string blobs using
URL.createObjectURL(new Blob([workerCode], { type: 'application/javascript' })). - 100% Obsidian Community Plugin Compliant: Bundles cleanly into a single
main.jsfile. Requires zero external.worker.jsfiles on disk, zero network requests, and zero permissions. - Unthrottled Off-Main-Thread Speed:
- Heavy $O(N)$ operations—including cache set diffing, identity key hashing, string comparisons, iCalendar (ICS) payload parsing, and RRule recurrence expansions—run in an isolated background V8 OS thread.
- The Web Worker runs at unthrottled maximum CPU speed (0ms artificial pauses). Because it executes on a separate OS thread, 100% CPU utilization in the worker causes 0ms delay and zero frame drops on the main Obsidian UI thread.
- Transparent Main-Thread Fallback:
- If Web Workers are restricted by custom environment security settings or mobile platform constraints,
WorkerManagerautomatically falls back to time-budgeted main-thread execution with zero errors or console warnings.
2. Native Chromium scheduler.yield() & 6ms Frame Execution Budget Guard¶
- Native Task Scheduling: For tasks that must run on the main thread (such as Obsidian vault file reads via
app.vault.read()or DOM updates), execution utilizes Chromium 115+ nativescheduler.yield()(andsetTimeout(0)macrotask fallback). - 6ms Budget Ceiling: At 60 FPS, an animation frame budget is 16.6ms. Main-thread loops evaluate
if (performance.now() - frameStart >= 6)and yield to Chromium when 6ms of CPU time elapses. - Guaranteed UI Responsiveness: Main-thread tasks never consume more than 6ms per frame tick, leaving >10.6ms every frame for 100% fluid, zero-lag user input (typing, mouse clicks, scrolling) and DOM rendering.
Load Debug Profiler (LoadDebugProfiler)¶
A high-performance diagnostic timing engine tracks plugin startup, staging, and indexing performance:
Zero-Overhead Architecture¶
- When disabled (
LoadDebugProfiler.isEnabled === false), all profiler entry points (startStage,startProvider,endProvider,startPhase,endPhase,recordYield) evaluateif (!this.enabled) return;immediately on entry.
Metrics Captured¶
- Startup Milestones:
onloadDurationMs: Time spent inplugin.onload().timeToLayoutReadyMs: Delay from plugin start until Obsidian workspace layout is ready.layoutReadyToPopulateMs: Delay from layout ready until cache population starts.totalPopulateDurationMs: Total wall-clock time required for cache population.- Stage & Provider Metrics:
- Total duration and provider breakdowns for
Stage 1 Local,Stage 2 Local,Stage 1 Remote,Stage 2 Remote. - Individual provider status (
OK/FAILED), event counts, and timing. - Internal Processing Phases:
Cache Delta Sync & Indexing: Measure of 3-way set diffing, identity re-mapping, andEventStoreupdates inCacheSyncHandler.TimeEngine Setup & Map Building: Timing of initialTimeEngineinitialization and global provider identifier map creation.- Overhead & Post-Processing:
totalYieldDurationMs: Accumulated time spent yielding to the main thread across allyieldToMainThread()calls.unaccountedDurationMs: Remaining duration computed viatotalPopulateDuration - (stages + phases + yields), guaranteeing 100% accounting.
UI & Inspection Architecture¶
- On-Demand Benchmark Modal:
showLoadDebugLogModalusesCopyTextModalto display a formatted breakdown, copy the report to clipboard, or re-run a live benchmark on demand viarunBenchmarkAndGetReport().
Efficient In-Memory Indexing (EventStore)¶
To ensure that dragging events and switching views remains fluid, the EventStore maintains multiple synchronous indexes:
- Primary Map:
SessionID -> Event. (O(1) access for UI updates). - Calendar Index:
CalendarID -> Set<SessionID>. (Fast filtering when toggling calendar visibility). - Path Index:
FilePath -> Set<SessionID>. (Instant updates when a file is modified externally).
Optimistic UI & Rollback¶
Every user-initiated change (drag, resize, edit) follows an Optimistic Pattern:
1. The EventCache updates the in-memory EventStore and notifies the UI immediately.
2. The UI re-renders without waiting for file I/O or network responses.
3. The ProviderRegistry attempts the durable write in the background.
4. Failure Path: If the write fails (e.g., network timeout, file locked), the cache rolls back the in-memory change and triggers a second UI update to revert the event to its original position.
Memory Management¶
- Event Pruning: Remote providers (like Google) implement a rolling cache. Events far outside the viewport are eventually purged from memory and re-fetched as needed to prevent unbounded memory growth.
- Stateless Enhancers: Normalization (Timezones, Categories) is performed by stateless functions to avoid object-bloat and reference-leakage.