Architecture Overview
This document describes the high-level architecture of IFClite, including both client-side and server-side processing paradigms.
System Architecture
IFClite supports two processing paradigms: client-side (WASM in browser) and server-side (native Rust). It provides multi-model federation for loading and managing multiple IFC models simultaneously with unified selection, visibility control, and coordinated ID spaces.
Layer Overview
flowchart TB
subgraph Clients["Clients"]
direction LR
Web["Web App"]
Desktop["Desktop"]
CLI["CLI"]
end
subgraph Features["Feature Layers"]
direction LR
Federation["Multi-Model Federation"]
BCFFeature["BCF"]
IDSFeature["IDS"]
Drawings["2D Drawings"]
MutationsFeature["Mutations"]
end
subgraph APIs["APIs"]
direction LR
TS["TypeScript"]
WASM["WASM"]
Rust["Rust"]
end
subgraph Storage["Storage"]
direction LR
Tables["Columnar Tables"]
Graph["Relationship Graph"]
GPU["GPU Buffers"]
end
Clients --> Features
Features --> APIs
APIs --> Storage
Processing Paradigms
The system offers two processing paths depending on your needs:
flowchart LR
subgraph ClientPath["Client-Side (WASM)"]
direction TB
C1["Parser"]
C2["Geometry"]
C3["Renderer"]
C1 --> C2 --> C3
end
subgraph ServerPath["Server-Side (Native)"]
direction TB
S1["Parser"]
S2["Geometry"]
S3["Parquet Encoder"]
S4["Content Cache"]
S1 --> S2 --> S3 --> S4
end
Client vs Server Paradigm
| Aspect |
Client-Side (WASM) |
Server-Side (Rust) |
| Processing |
Single-threaded |
Multi-threaded (Rayon) |
| Memory |
4GB WASM limit |
System RAM |
| Caching |
Browser storage |
Content-addressable disk |
| Format |
Raw geometry |
Parquet (15-50x smaller) |
| Best For |
Privacy, offline |
Teams, large files |
Design Principles
1. Zero-Copy Where Possible
Data flows through the system with minimal copying:
flowchart LR
subgraph Traditional["Traditional Approach"]
T1["File Buffer"]
T2["Parse to Objects"]
T3["Convert to Arrays"]
T4["Upload to GPU"]
T1 -->|copy| T2 -->|copy| T3 -->|copy| T4
end
subgraph IFCLite["IFClite Approach"]
I1["File Buffer"]
I2["Direct Index"]
I3["TypedArrays"]
I4["GPU Upload"]
I1 -->|reference| I2 -->|view| I3 -->|share| I4
end
style Traditional fill:#dc2626,stroke:#7f1d1d,color:#fff
style IFCLite fill:#16a34a,stroke:#14532d,color:#fff
2. Streaming First
Process data incrementally for responsive UIs:
sequenceDiagram
participant File
participant Parser
participant Processor
participant Renderer
participant User
File->>Parser: Chunk 1
Parser->>Processor: Entities 1-100
Processor->>Renderer: Meshes 1-50
Renderer->>User: First render (300ms)
File->>Parser: Chunk 2
Parser->>Processor: Entities 101-200
Processor->>Renderer: Meshes 51-100
Note over User: Progressive loading
File->>Parser: Chunk N
Parser->>Processor: All entities
Processor->>Renderer: All meshes
Renderer->>User: Complete
3. On-Demand Property Extraction
Properties parsed lazily for faster initial load:
flowchart LR
subgraph TraditionalParsing["Traditional"]
T1["Parse All Entities"]
T2["Parse All Properties"]
T3["Build All Tables"]
T1 --> T2 --> T3
end
subgraph OnDemand["IFClite On-Demand"]
O1["Parse Entities"]
O2["Build Index"]
O3["Map: entityId → psetIds"]
O4["Parse on Access"]
O1 --> O2 --> O3
O3 -.->|"lazy"| O4
end
style TraditionalParsing fill:#dc2626,stroke:#7f1d1d,color:#fff
style OnDemand fill:#16a34a,stroke:#14532d,color:#fff
4. Columnar Storage
Store data in columnar format for cache-efficient access:
graph TB
subgraph RowBased["Row-Based (Traditional)"]
R1["Entity 1: id=1, type=WALL, name='A'"]
R2["Entity 2: id=2, type=DOOR, name='B'"]
R3["Entity 3: id=3, type=WALL, name='C'"]
end
subgraph Columnar["Columnar (IFClite)"]
C1["IDs: Uint32Array [1, 2, 3, ...]"]
C2["Types: Uint16Array [WALL, DOOR, WALL, ...]"]
C3["Names: StringTable ['A', 'B', 'C', ...]"]
end
5. Hybrid Data Model
Combine the best of different data structures:
| Data Structure |
Use Case |
Access Pattern |
| Columnar Tables |
Bulk queries, filtering |
Sequential scan |
| CSR Graph |
Relationship traversal |
Adjacency lookup |
| On-Demand Maps |
Property access |
Hash lookup |
| BVH |
Raycasting |
Tree traversal |
Package Architecture
The monorepo contains 18 TypeScript packages, 4 Rust crates, and multiple application targets.
graph TB
subgraph Rust["Rust Crates"]
Core["ifc-lite-core<br/>Parsing"]
Geo["ifc-lite-geometry<br/>Triangulation"]
Wasm["ifc-lite-wasm<br/>Bindings"]
Server["ifc-lite-server<br/>HTTP API"]
end
subgraph TS["TypeScript Packages (18)"]
Parser["@ifc-lite/parser"]
IFCX["@ifc-lite/ifcx"]
Geometry["@ifc-lite/geometry"]
Renderer["@ifc-lite/renderer"]
ServerClient["@ifc-lite/server-client"]
ServerBin["@ifc-lite/server-bin"]
Cache["@ifc-lite/cache"]
Query["@ifc-lite/query"]
Data["@ifc-lite/data"]
Export["@ifc-lite/export"]
BCF["@ifc-lite/bcf"]
IDS["@ifc-lite/ids"]
Drawing2D["@ifc-lite/drawing-2d"]
Mutations["@ifc-lite/mutations"]
Spatial["@ifc-lite/spatial"]
Codegen["@ifc-lite/codegen"]
WasmTS["@ifc-lite/wasm"]
CreateCLI["@ifc-lite/create-ifc-lite"]
end
subgraph Apps["Applications"]
Viewer["Viewer App"]
Desktop["Desktop (Tauri)"]
CLI["create-ifc-lite"]
end
Wasm --> Core
Wasm --> Geo
Parser --> WasmTS
WasmTS --> Wasm
Geometry --> WasmTS
Renderer --> Geometry
ServerClient --> Server
Query --> Data
Export --> Data
BCF --> Data
IDS --> Data
Drawing2D --> Geometry
Mutations --> Data
Spatial --> Data
Viewer --> Parser
Viewer --> Renderer
Viewer --> ServerClient
Desktop --> Core
Desktop --> Geo
Server Architecture
flowchart TB
subgraph Client["Browser Client"]
Hash["SHA-256 Hash"]
Upload["File Upload"]
Decode["Parquet Decoder"]
Render["WebGPU Renderer"]
end
subgraph Server["Rust Server (Axum)"]
Router["API Router"]
Parser["IFC Parser"]
GeoProc["Geometry Processor"]
DataModel["Data Model Extractor"]
Serializer["Parquet Serializer"]
end
subgraph Cache["Cache Layer"]
DiskCache[(Disk Cache)]
end
Hash -->|"check"| Router
Router -->|"hit"| DiskCache
DiskCache --> Decode
Upload -->|"miss"| Parser
Parser --> GeoProc
Parser --> DataModel
GeoProc --> Serializer
DataModel --> Serializer
Serializer --> DiskCache
Serializer --> Decode
Decode --> Render
style Client fill:#6366f1,stroke:#312e81,color:#fff
style Server fill:#10b981,stroke:#064e3b,color:#fff
style Cache fill:#f59e0b,stroke:#7c2d12,color:#fff
Server Cache Strategy
sequenceDiagram
participant Client
participant Server
participant Cache
Client->>Client: Compute SHA-256 hash
Client->>Server: GET /cache/check/{hash}
alt Cache Hit
Server->>Cache: Lookup
Cache-->>Server: Parquet data
Server-->>Client: 200 (skip upload!)
else Cache Miss
Server-->>Client: 404
Client->>Server: POST /parse/parquet
Server->>Server: Parse (parallel)
Server->>Cache: Store
Server-->>Client: Parquet response
end
Data Flow
Client-Side Parse Flow
Each model is parsed independently and then registered with the FederationRegistry, which assigns non-overlapping ID ranges (idOffset) so that multiple models can coexist with unique global IDs (globalId = localExpressId + model.idOffset).
flowchart TB
Input["IFC File<br/>(ArrayBuffer)"]
subgraph Tokenize["1. Tokenize"]
STEP["STEP Lexer"]
Tokens["Token Stream"]
end
subgraph Scan["2. Scan"]
EntityScan["Entity Scanner"]
Index["Entity Index"]
end
subgraph Decode["3. Decode"]
Decoder["Entity Decoder"]
Attrs["Attributes"]
end
subgraph Store["4. Store"]
Tables["Columnar Tables"]
Graph["Relationship Graph"]
OnDemand["On-Demand Maps"]
end
subgraph Federate["5. Federate"]
Registry["FederationRegistry"]
GlobalIDs["Global ID Assignment"]
end
Output["IfcDataStore"]
Input --> Tokenize
Tokenize --> Scan
Scan --> Decode
Decode --> Store
Store --> Federate
Federate --> Output
style Input fill:#6366f1,stroke:#312e81,color:#fff
style Tokenize fill:#2563eb,stroke:#1e3a8a,color:#fff
style Scan fill:#10b981,stroke:#064e3b,color:#fff
style Decode fill:#f59e0b,stroke:#7c2d12,color:#fff
style Store fill:#a855f7,stroke:#581c87,color:#fff
style Federate fill:#ec4899,stroke:#831843,color:#fff
style Output fill:#16a34a,stroke:#14532d,color:#fff
Server-Side Parse Flow
flowchart TB
Input["IFC File"]
subgraph Parse["1. Parse (Parallel)"]
Tokenize["STEP Tokenizer"]
Extract["Entity Extractor"]
end
subgraph Process["2. Process (Parallel)"]
Geo["Geometry Extraction"]
Data["Data Model Extraction"]
end
subgraph Serialize["3. Serialize"]
ParquetGeo["Parquet Geometry"]
ParquetData["Parquet Data Model"]
end
subgraph Cache["4. Cache"]
Store["Disk Cache"]
end
Output["Parquet Response"]
Input --> Parse
Parse --> Process
Geo --> ParquetGeo
Data --> ParquetData
ParquetGeo --> Store
ParquetData --> Store
Store --> Output
style Input fill:#6366f1,stroke:#312e81,color:#fff
style Parse fill:#2563eb,stroke:#1e3a8a,color:#fff
style Process fill:#10b981,stroke:#064e3b,color:#fff
style Serialize fill:#f59e0b,stroke:#7c2d12,color:#fff
style Cache fill:#a855f7,stroke:#581c87,color:#fff
Render Flow
flowchart TB
subgraph Input["Input"]
Meshes["Mesh Data"]
Camera["Camera State"]
end
subgraph Process["Processing"]
Cull["Frustum Culling"]
Sort["Depth Sort"]
Batch["Batching"]
end
subgraph Upload["GPU Upload"]
Vertex["Vertex Buffers"]
Index["Index Buffers"]
Uniform["Uniforms"]
end
subgraph Render["Render"]
Pass["Render Pass"]
Section["Section Planes"]
Draw["Draw Calls"]
end
Output["Canvas"]
Input --> Process
Process --> Upload
Upload --> Render
Render --> Output
style Input fill:#6366f1,stroke:#312e81,color:#fff
style Process fill:#2563eb,stroke:#1e3a8a,color:#fff
style Upload fill:#10b981,stroke:#064e3b,color:#fff
style Render fill:#f59e0b,stroke:#7c2d12,color:#fff
style Output fill:#a855f7,stroke:#581c87,color:#fff
Memory Architecture
In a multi-model federation scenario, each loaded model maintains its own data store in the JS heap. The FederationRegistry tracks ID ranges per model to enable O(1) global-to-local ID resolution without duplicating entity data across models.
graph TB
subgraph JS["JavaScript Heap"]
Strings["String Table"]
Metadata["Entity Metadata"]
Query["Query Results"]
OnDemand["On-Demand Maps"]
FedRegistry["FederationRegistry"]
Models["Models Map"]
end
subgraph Wasm["WASM Linear Memory"]
Parser["Parser State"]
Geometry["Geometry Processing"]
Buffers["Mesh Buffers"]
end
subgraph GPU["GPU Memory"]
VBO["Vertex Buffers"]
IBO["Index Buffers"]
UBO["Uniform Buffers"]
ID["ID Buffer (Picking)"]
end
Wasm -->|"Zero-copy view"| JS
Wasm -->|"Direct upload"| GPU
Memory Efficiency
| Component |
Memory Strategy |
| Strings |
Deduplicated string table (30% reduction) |
| Entity IDs |
Uint32Array (fixed-size) |
| Types |
Uint16Array enum (2 bytes vs ~20 for string) |
| Properties |
On-demand parsing (not pre-loaded) |
| Geometry |
Streaming + dispose after upload |
| Server |
Parquet (15-50x smaller transfer) |
Threading Model
Client-Side
flowchart LR
subgraph Main["Main Thread"]
UI["UI Events"]
Render["Rendering"]
Query["Queries"]
end
subgraph Worker["Web Worker"]
Parse["Parsing"]
Geo["Geometry"]
end
Main <-->|"Transferable"| Worker
Server-Side
flowchart TB
subgraph Axum["Axum Runtime"]
Router["Async Router"]
Handlers["Request Handlers"]
end
subgraph Rayon["Rayon Thread Pool"]
Parser1["Parser Thread 1"]
Parser2["Parser Thread 2"]
ParserN["Parser Thread N"]
end
subgraph Tokio["Tokio Runtime"]
Cache["Cache I/O"]
Response["Response Stream"]
end
Router --> Handlers
Handlers -->|"spawn_blocking"| Rayon
Handlers --> Tokio
IFC5 (IFCX) Architecture
flowchart TB
subgraph Input["IFCX File"]
JSON["JSON Structure"]
Header["Header"]
Schemas["Schemas"]
Data["Data Nodes"]
end
subgraph Composition["ECS Composition"]
Flatten["Flatten Layers"]
Inherit["Resolve Inheritance"]
Tree["Build Tree"]
end
subgraph Extract["Extraction"]
Entities["Entity Extractor"]
Props["Property Extractor"]
Geo["Geometry Extractor"]
Hierarchy["Hierarchy Builder"]
end
subgraph Output["Output"]
Store["IfcxParseResult"]
Meshes["Pre-tessellated Meshes"]
end
Input --> Composition
Composition --> Extract
Extract --> Output
style Input fill:#6366f1,stroke:#312e81,color:#fff
style Composition fill:#10b981,stroke:#064e3b,color:#fff
style Extract fill:#f59e0b,stroke:#7c2d12,color:#fff
style Output fill:#a855f7,stroke:#581c87,color:#fff
Extension Points
graph TB
subgraph Core["Core System"]
Parser["Parser"]
Geometry["Geometry"]
Renderer["Renderer"]
end
subgraph Extensions["Extension Points"]
CustomExtractor["Custom Extractors"]
CustomProcessor["Custom Processors"]
CustomShaders["Custom Shaders"]
Plugins["Plugins"]
end
CustomExtractor -.->|extends| Parser
CustomProcessor -.->|extends| Geometry
CustomShaders -.->|extends| Renderer
Plugins -.->|hooks| Core
Adding Custom Geometry Processor
import { GeometryProcessor, ProcessorRegistry } from '@ifc-lite/geometry';
class CustomProcessor extends GeometryProcessor {
canProcess(entity: Entity): boolean {
return entity.type === 'IFCMYCUSTOMTYPE';
}
process(entity: Entity): Mesh {
// Custom processing logic
return mesh;
}
}
ProcessorRegistry.register(new CustomProcessor());
Technology Stack
graph TB
subgraph Languages["Languages"]
Rust["Rust"]
TS["TypeScript"]
WGSL["WGSL (Shaders)"]
end
subgraph Runtime["Runtime"]
WASM["WebAssembly"]
WebGPU["WebGPU"]
Browser["Browser"]
Node["Node.js"]
Tauri["Tauri"]
end
subgraph Build["Build Tools"]
Cargo["Cargo"]
Vite["Vite"]
WasmPack["wasm-pack"]
Turborepo["Turborepo"]
end
subgraph Formats["Data Formats"]
STEP["STEP (IFC4)"]
IFCX["IFCX (IFC5)"]
Parquet["Apache Parquet"]
Cache["Binary Cache"]
end
Rust --> WASM
Rust --> Tauri
TS --> Browser
TS --> Node
WGSL --> WebGPU
style Languages fill:#6366f1,stroke:#312e81,color:#fff
style Runtime fill:#10b981,stroke:#064e3b,color:#fff
style Build fill:#f59e0b,stroke:#7c2d12,color:#fff
style Formats fill:#a855f7,stroke:#581c87,color:#fff
Next Steps