Architecture

How SolverForge uses WASM and HTTP to solve constraints

SolverForge uses a layered architecture that separates constraint definition (Rust) from solving execution (Java/Timefold).

Overview

┌─────────────────────────────────────────────────────────────────┐
│                    Your Application (Rust)                       │
│  • Define domain model                                          │
│  • Build constraints                                            │
│  • Generate WASM predicates                                     │
└─────────────────────────────────────────────────────────────────┘
                              │
                         HTTP/JSON
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│               timefold-wasm-service (Java)                       │
│  • Execute WASM predicates via Chicory runtime                  │
│  • Run Timefold solver algorithms                               │
│  • Return optimized solution                                    │
└─────────────────────────────────────────────────────────────────┘

Why This Architecture?

WASM for Portability

Constraint predicates are compiled to WebAssembly, which:

  • Runs safely in a sandboxed environment
  • Executes at near-native speed
  • Works across different language runtimes

HTTP for Simplicity

Using HTTP/JSON instead of JNI provides:

  • Clean separation between Rust and Java
  • Easy debugging (inspect JSON requests/responses)
  • Language-agnostic interface for future bindings

Components

solverforge-core (Rust)

The core library provides:

ModulePurpose
domainDefine planning entities and fields
constraintsBuild constraint streams
wasmGenerate WASM modules
solverConfigure solver and send requests
scoreScore types (HardSoft, Bendable, etc.)

timefold-wasm-service (Java)

The solver service handles:

ComponentPurpose
Chicory WASM RuntimeExecute constraint predicates
Dynamic Class GenerationCreate Java classes from domain DTOs
Timefold SolverRun optimization algorithms
Host FunctionsBridge WASM calls to Java operations

Request Flow

1. Build Domain Model      → DomainModel with annotations
2. Create Constraints      → Constraint streams (forEach, filter, penalize)
3. Generate WASM           → Predicates compiled to WebAssembly
4. Build SolveRequest      → JSON payload with domain + constraints + WASM + problem
5. Send HTTP POST          → /solve endpoint
6. Solver Executes         → Timefold evaluates constraints via WASM
7. Return Solution         → JSON response with score and assignments

WASM Memory Layout

Domain objects are stored in WASM linear memory with proper alignment:

TypeAlignmentSize
int, float, pointer4 bytes4 bytes
long, double, DateTime8 bytes8 bytes

Example Shift layout:

Field            Offset  Size  Type
───────────────────────────────────
id               0       4     String (pointer)
employee         4       4     Employee (pointer)
location         8       4     String (pointer)
[padding]        12      4     (align for DateTime)
start            16      8     LocalDateTime
end              24      8     LocalDateTime
requiredSkill    32      4     String (pointer)
───────────────────────────────────
Total: 40 bytes

Both Rust (WASM generation) and Java (runtime) use identical alignment rules.

Host Functions

WASM predicates can call host functions for operations that require Java:

FunctionPurpose
string_equalsCompare two strings
list_containsCheck if list contains element
ranges_overlapCheck if time ranges overlap
hroundRound float to integer

These are injected into the WASM module via HostFunctionRegistry.