Thousands of tasks, milliseconds
Pick a plan shape and a size, press Run, and the engine solves, previews, levels and compares a plan of up to 10,000 tasks in your browser. Each bar fills as its call returns. There is no Gantt chart on purpose: drawing ten thousand bars would time the renderer, not the engine.
The other demos run on ten-task plans so you can see what the engine does. This one shows how fast it does it at real sizes, on your machine, with nothing sent to a server.
Four calls, timed one by one
A run builds the plan in plain JavaScript (that bar is grey and not counted as engine time), then times four calls. solve schedules every task. preview answers "what if I drag this bar?" fifty times on the solved plan, and the demo shows the median. level removes the overbookings by delaying or splitting tasks. evaluate scores moving one task to each of up to nine people, leveling the plan again for every candidate, so it is the slowest call and comes last.
// Each call is timed on its own with performance.now(), JSON in and out included.
const project = new Project();
const solved = project.solve(data, { includeUnchanged: true }); // 1. every task's dates
for (let i = 0; i < 50; i++) { // 2. "what if I move this bar?"
project.preview({ tasks: { update: [{ id, start_date }] } }); // the median is shown
}
const leveled = project.level({ allowSplits: true }); // 3. remove overbookings
project.evaluate(candidates, { level: { allowSplits: true } }); // 4. who should take the bottleneck task?
project.dispose();The engine total in the board is solve + one preview + level + evaluate. Every timing includes turning the input into JSON and parsing the result, because a real app pays for that too. The board shows the payload sizes next to the result.
Plan shapes
Chain, Wide fan-out and Islands are the task graphs of the engine benchmark: one long chain, one task with everyone else waiting on it, and chains of ten. Fork/join batches is the benchmark's leveling workload, unchanged. The benchmark runs the first three without people, so the demo adds them: tasks are dealt round-robin to the chosen number of resources, each able to do one thing at a time. These are resource-augmented benchmark graphs, not the benchmark workloads themselves: thesolve input differs from the benchmark's, and the level, evaluate andpreview sequences are the demo's own. Compare shapes with each other here, and with the published benchmark only for the graph they share.
A Chain never overbooks anyone, because its one-day tasks follow each other, so it is the baseline with nothing to level. Tangled is the demo's own worst case: every task has two or three predecessor links, about half of them start-to-start, with lags of up to three days, and every resource is overbooked. Leveling it takes many rounds, and evaluate repeats that for every candidate, which is why it is slower and the board says so.
Off the main thread
The calls run in a Web Worker. On the main thread a one-second call freezes the page, and a frozen page makes that second feel longer than it is. The worker keeps the page responsive, sends back only numbers and a summary, and is simply terminated when you press Cancel.
// The board never sees the plan. The worker posts numbers back as each call returns.
const worker = new Worker(new URL("./lib/speed.worker.js", import.meta.url), { type: "module" });
worker.postMessage({ type: "run", runId, shape, size, resources, seed });
// in: { type: "step", runId, step: "solve", ms }
// { type: "step", runId, step: "evaluate", ms, extra: { candidates, failed } }
// { type: "done", runId, summary }
// Cancel: worker.terminate(). Engine calls are synchronous, so there is nothing else to interrupt.Who should take the bottleneck task?
The evaluate step picks the first task of the worst overbooked window after solving, or the task that ends last when nobody is overbooked, and asks the engine what happens if each of the first resources takes it. The bar label shows how many candidates were scored, and a candidate the engine rejects is counted, not hidden.
// The task at the front of the worst overbooked window, offered to each of the first resources.
const candidates = resources.slice(0, 9).map(r => ({
id: r.id,
changes: { assignments: { update: [{ id: assignment.id, resource: r.id }] } },
}));
const result = project.evaluate(candidates, { level: { allowSplits: true } });
// result.candidates[i]: { scheduled, leveled, cost } or { error }, and the project is untouchedYour machine, your numbers
Every number on this page was measured just now in your browser, on one thread, with the engine compiled to WebAssembly. Shape and size pairs that took more than three seconds on our test machine start only on a second click. Run all walks every pair and keeps every result in the history table, so you can compare shapes and sizes side by side; on those slow pairs it times solve and preview and markslevel and evaluate skipped.
The next demo puts the same engine behind a single Gantt chart: drag one bar and watch the plan follow.