User:Tim.Jenssen/ProjectStorage Architecture

From Qt Wiki
Jump to navigation Jump to search
AI generated content: The content of this page was generated by AI as an experiment.

Overview

ProjectStorage is the SQLite-backed replacement for the old QMLJS code model. It provides a unified source of truth for QML type information (types, imports, properties, annotations, prototypes, extensions) and serves it via the NodeMetaInfo API to all views.

Benefits (persistent DB + incremental updates)

  • **Faster** startup/reloads
  • **Consistent** cross-view data
  • **Clear separation** of filesystem → parsing → storage

At runtime ProjectStorage

  • Parses QML / QML types (QmlDocumentParser, QmlTypesParser)
  • Synchronizes into SQLite (IDs, links)
  • Serves fast **ID-based** queries
  • Tracks file changes (watcher + status cache)
  • Notifies dependents on change

Architecture

+-----------------------------------------------------------------+
|                   Qt Design Studio Frontend                     |
|(Navigator, FormEditor, PropertyEditor, States, Timeline, etc.)  |
+--------------------^--------------------------------------------+
                     | NodeMetaInfo / Model API (IDs)
                     v
            +--------------------------------------+
            |       QmlDesigner::ProjectStorage     |
            |  - synchronize(SynchronizationPackage)|
            |  - type/import/alias/property APIs    |
            +--------------------------------------+
                     |                   ^
                     |                   |
                     v                   |
    +----------------+--------+    +-----+--------------------+
    | QmlDocumentParser       |    | QmlTypesParser           |
    | (.qml -> Types, Imports)|    | (.qmltypes -> Exports)   |
    +--------------------------+    +--------------------------+
                     |
                     v
            +--------------------------------------+
            |  SQLite (IDs, Types, Imports, Links) |
            +--------------------------------------+
                     ^
                     |
    +----------------+----------------------+
    | FileStatusCache + FileSystem          |
    | + ProjectStorageUpdater + PathWatcher |
    +---------------------------------------+

Runtime Flow

1) Initialization

  • In-memory SQLite by default
  • Common built-ins and module metadata preloaded (CommonTypeCache, ModulesStorage).

2) Parse → Synchronize

  • `QmlDocumentParser::parse(...)` → Storage::Synchronization::Type + Imports.
  • `QmlTypesParser::parse(...)` → ExportedTypes.
  • Synchronize in one call (example):
    projectStorage.synchronize(package);
    
  • During sync: upsert types/aliases/defaults, link prototypes/extensions, clean removed items, notify observers.

3) Incremental updates

  • ProjectStorageUpdater listens to watcher events, computes deltas, and rebuilds a minimal SynchronizationPackage before calling sync.
  • Only affected sources are reparsed and stored.
  • Duplicates in exported types are removed to keep the DB canonical.

4) File status tracking

  • FileStatusCache::modified(SourceIds) compares cached size/mtime with current filesystem state; returns changed SourceIds.
  • Missing entries are added and considered modified on first sighting; results are sorted and de-duplicated.

5) Watching

  • Directory-level watching; events compressed to avoid storms; parent directories considered where needed.
  • Watcher computes changed entries using cached FileStatus vs. fresh FileStatus and emits:
    • `pathsChanged(SourceIds)`
    • `pathsWithIdsChanged(std::vector<IdPaths>)` (grouped by project part and source type)

6) Query & access

  • Views/tooling query via IDs (TypeId, ImportId, PropertyDeclarationId …).
  • Names are for display/debugging; hot paths avoid string lookups.

Synchronization Package (inputs)

The updater constructs a rich delta object and passes it to ProjectStorage::synchronize:

  • **Imports** + **updatedImportSourceIds**
  • **ExportedTypes** + **updatedExportedTypeSourceIds**
  • **Types** + **updatedTypeSourceIds**
  • **FileStatuses** + **updatedFileStatusSourceIds**
  • **ProjectEntryInfos** + **updatedProjectEntryInfoSourceIds**
  • **Module dependencies** + **updatedModuleDependencySourceIds**
  • **ModuleExportedImports** + **updatedModuleIds**
  • **PropertyEditorQmlPaths** + **updatedPropertyEditorQmlPathDirectoryIds**
  • **TypeAnnotations** + **updatedTypeAnnotationSourceIds**

Behavior

A) Updated-ID hygiene & de-duplication

  • **Duplicate exported types are removed** before sync:
removeDuplicates(package.exportedTypes);

Sorted + unique → clean set passed to storage.

  • **Updated file statuses** are filtered against “not updated” sets to avoid false positives:
package.updatedFileStatusSourceIds = filterNotUpdatedSourceIds(...);

B) Filesystem truth via FileStatusCache

  • modified():
    • Intersects known cache with incoming SourceIds; if size/mtime differ → push to modified.
    • Adds any unseen SourceId (first encounter counts as modified) and merges into cache.
    • Returns **sorted** list to keep downstream set operations stable.
  • find() and updateAndFind(): Lower-bound lookup by SourceId; insert/update lazily; both are traced for debugging.
  • remove() (by directory or sources): greedy-difference against cached entries; keeps only “not removed” file statuses.

C) QMLDIR + directory scanning

  • **QMLDIR components** become exported types using normalized paths and module IDs; version is converted to storage format:
    addExportedTypesFromQmldir(..., moduleId, ...);
    
  • **Directory `.qml` files** populate exported types for PathLibrary modules:
    addExportedTypesFromDirectory(qmlFileNames, directoryId, pathModuleId, ...);
    
  • **Module exported imports** from QMLDIR are transformed into canonical “exported imports” rows for both QML and C++ module kinds (auto-version handled).

D) ProjectEntryInfos & removed sources

  • When a directory changes, updater:
    • Pushes updatedExportedTypeSourceIds for the directory
    • Computes **removed** QML documents by set-difference (stored entries vs. current files) and marks their **import/type SourceIds** as updated to force cleanup:
      addRemovedImportAndTypeSourceIds(...);
      
    • Emits updatedProjectEntryInfoSourceIds for directory metadata refresh.

E) PropertyEditor QML paths

  • Updater scans designated resource roots and adds per-module editor QML mappings; directory changes trigger updates of updatedPropertyEditorQmlPathDirectoryIds.

F) Type annotations (.metainfo)

  • Recursive directory scan for `*.metainfo`; parses each file and appends TypeAnnotations into the package:
    updateTypeAnnotation(directory, filePath, sourceId, dirId, package);
    
  • **UpdatedTypeAnnotationSourceIds** are computed per directory by merging changed and stored SourceIds; directories with state change aggregate both stored-changed and newly updated:
    updateTypeAnnotationDirectories(package, notUpdated, updatedSrcDict);
    

G) Watcher → Notifier contract

  • Watcher groups changed entries by ProjectPartId and SourceType into IdPaths, then notifies:
  m_notifier->pathsChanged(watchedSourceIds);
  m_notifier->pathsWithIdsChanged(changedIdPaths);

Entries are **sorted** and **unique** per group to stabilize the UI/model updates.

H) Filesystem implementation details

  • File sizes and timestamps come from `std::filesystem::directory_entry`; errors are tolerated (size set to 0 with error_code).
  • Directory enumeration uses Qt’s QDir/QDirIterator; SourceIds are obtained through the SourcePathCache.

Practical Guidance

Minimal updater call

ProjectStorageUpdater::Update u;
u.qtDirectories = {/* Qt module dirs */};
u.projectDirectory = /* project root */;
u.propertyEditorResourcesPath = /* optional resource dir */;
u.typeAnnotationPaths = {/* zero or more roots with *.metainfo */};
updater.update(std::move(u));

Typical call flow

File change detected
    ↓
ProjectStoragePathWatcher::pathsChanged()
    ↓
ProjectStorageUpdater::update()
    ↓
  parse QML / QMLTYPES / metainfo
    ↓
Build SynchronizationPackage
    ↓
ProjectStorage::synchronize()
    ↓
SQLite updated, Observers notified

Debugging & Tracing

  • Build with ENABLE_PROJECT_STORAGE_TRACING → Perfetto/Nanotrace records for: update(), file-status checks, QMLDIR scan, directory traversal, annotation parsing, and synchronization.
  • Persist DB (set env var QDS_STORE_PROJECTSTORAGE_IN_PROJECT) to inspect with DB Browser for SQLite.

Design Decisions (recap)

  • **IDs over strings** in all hot paths; displayName() is UI-only.
  • **No fully qualified type names** stored in nodes; qualification through imports.
  • **Versions** live in imports, not per node.
  • **Subcomponent manager removed**; ProjectStorage owns project .qml components.