Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

V2 Generic Engine

The V2 calculation core is a domain-neutral Rust library linked into the xaligo executable. It does not run as a daemon, sidecar, dynamic plugin, or separate command:

Go use case -> cgo -> C ABI v2 -> Rust staticlib

The public Go use-case boundary remains:

type EngineUsecase interface {
    Resolve(context.Context, entity.EngineDocumentSpec) (entity.EngineResolvedDocument, error)
    RenderSVG(context.Context, entity.EngineDocumentSpec) ([]byte, error)
    NormalizeSVG(context.Context, []byte) (entity.EngineSVG, error)
}

The generic calculation core and this ABI are implemented. The <xaligo version="2"> frontend lowers directly to EngineDocumentSpec, and the ordinary render use case projects its resolved result through the shared SVG/PPTX document plan. Its authoring profile preserves V1-style rows, columns, AWS groups, catalog items, ports, and connections while keeping generic V2 parameters available as explicit extensions. Profile normalization is one linear tree walk; it does not reparse XML or run the V1 renderer. Catalog icon data and short labels share one load per render; the bundled default catalog is cached across renders.

Rust source structure

The engine is one staticlib crate and follows the responsibility structure of ryo-arima/vem/src:

external/engine/src/
├── lib.rs                  staticlib export boundary
├── mod.rs                  module registry
├── base.rs                 decode/execute/encode composition root
├── cnf/engine.rs           ABI constants, limits, and defaults
├── cnf/engine_abi.rs       generated ABI field constants
├── ent/model/              generic document and normalized SVG models
├── ent/request/engine.rs   binary request decoding
├── ent/response/engine.rs  binary response encoding
├── usc/engine.rs           operation dispatch
├── usc/cancel.rs           call-scoped cooperative cancellation
├── usc/layout.rs           layout resolution entry point and shared state
├── usc/layout_flow.rs      stack, grid, and absolute placement
├── usc/layout_geometry.rs  bounds and geometry helpers
├── usc/layout_routing.rs   ports and line routing
├── usc/layout_validation.rs input and constraint validation
├── usc/layout_tests.rs     layout regression tests
├── usc/svg.rs              SVG normalization and projection
├── rep.rs                  reserved for future encoders such as PPTX
├── ctl/engine.rs           panic-safe C ABI and owned buffers
└── util/
    ├── serialize.rs        explicit ABI response serialization
    ├── deserialize.rs      explicit bounded ABI request deserialization
    ├── clone/debug/eq.rs   explicit model trait implementations
    ├── default.rs          explicit neutral-value defaults
    ├── mcode.rs            engine message codes and log levels
    ├── logger.rs           Go-compatible environment-configured logging
    └── error.rs            layout and SVG errors

external/engine/abi/fields.csv is the single field-index schema. Running make generate-engine-abi regenerates both Go and Rust constants without introducing runtime reflection, serde, JSON, or arbitrary maps.

This preserves the cnf / ent / usc / ctl / util dependency vocabulary while placing calculation behavior in cohesive use-case files and without copying VEM’s CLI-specific main.rs. lib.rs is the corresponding library entry point. The C symbols and Go EngineUsecase contract are unchanged by this source-only reorganization.

The current data path is ctl -> usc -> ctl. The rep layer deliberately has no implementation because calculation results return directly through ctl. If PPTX package generation later moves into Rust, its external-representation writer belongs in a flat rep/pptx_*.rs slice and may be called from usc; layout, routing, and validation remain in usc. Layer directories stay shallow and use filenames such as layout_flow.rs instead of nested directories.

Engine-owned models intentionally have no derive or serde annotations. Standard traits and the binary ABI codecs are implemented explicitly under util, following VEM’s implementation pattern. serialize.rs and deserialize.rs continue to read and write the fixed-width little-endian ABI; they do not add a JSON or generic-map boundary.

Grouped imports use a vertical, one-item-per-line form throughout the Rust crate. This makes imports stable and reviewable as layer dependencies change.

Rust logging

util/mcode.rs defines structured engine message codes and util/logger.rs follows the Go shared logger contract: level filtering, optional structured JSON, component/service metadata, caller metadata, error field extraction, and XALIGO_LOG_* configuration. The static library adapts that contract in two ways: stderr is the default so Go protocol stdout remains machine-usable, and a Rust fatal log never terminates the embedding Go process. Engine failures are still returned through the typed ABI.

Default-level execution is silent because the composition root emits only debug lifecycle events. Logs do not include .xal contents or absolute caller paths.

Generic concepts

The engine accepts only the following calculation concepts:

ConceptCalculation responsibility
FrameCanvas, page, and top-level containment geometry
GroupNested content box and child layout
CaptureGeneric emphasis or annotation boundary
ItemAtomic or composed visual slot
PortAddressable endpoint placed inside its owner
LineStraight or orthogonal route between IDs
TextRenderer-neutral intrinsic text measurement and label data
SpacerNon-drawing layout participant

AWS, UML, and future vocabularies must lower to these concepts. No profile ID, source tag, icon namespace, aws, or uml discriminator crosses into a Rust calculation branch.

Layout and routing

The implemented policies are:

  • vertical and horizontal fixed-before-flex allocation;
  • per-child weights, margins, dimensions, intrinsic sizes, offsets, and min/max constraints;
  • generic grids, including a 12-column configuration and row/column spans;
  • adaptive item grids that select rows and columns from the available aspect ratio and shrink icons when label space requires it;
  • absolute placement;
  • nested content boxes with padding, gap, alignment, justification, and error or visible overflow;
  • owner-relative ports with side, anchor, offset, and explicit size; and
  • straight and deterministic orthogonal routes with generic obstacle scoring, endpoint decorations, line styles, and labels.

Every element carries independent typed parameters. Pointer fields on the Go request preserve unset values separately from explicit 0 and false. Invalid enum values, unknown ABI versions, non-finite numbers, bad ranges, duplicate IDs, malformed parent indexes, excessive nesting, invalid spans, and missing line endpoints fail before a renderer receives geometry.

The Rust SVG projection consumes the same immutable resolved document returned by Resolve. It draws generic shapes, text, ports, lines, labels, and endpoint decorations without recomputing layout or routing. SVG registration continues to pass through the separate safe normalization operation before SQLite stores it.

ABI v2

ABI v2 is a bounded binary contract. All fixed-width values are little-endian; variable text is UTF-8 with explicit lengths. An input tree is flattened once in pre-order and each record stores its parent index. Optional numeric and boolean fields use presence bitsets, so an omitted value cannot collapse into its zero value. Named string slots carry IDs, text, colors, icon references, and endpoints; arbitrary maps and renderer JSON are not accepted.

The resolved response remains in deterministic pre-order and contains:

  • parent identity and generic concept;
  • final finite (x, y, width, height) geometry;
  • resolved renderer-neutral visual and text values;
  • resolved text box plus selected icon reference and icon box; and
  • final line points, style, decorations, label, and label position.

The engine limits one request to 10,000 elements, 128 nesting levels, 16 MiB of ABI input, 32 MiB of ABI output, and 2 MiB for one normalized SVG. Rust owns the response allocation; Go copies it before invoking the matching C free function.

Go creates a C-owned atomic cancellation handle for each context-aware engine call. Rust checks it at bounded layout and routing boundaries and returns the ordinary typed error response; no callback, daemon, or subprocess is involved.

Frontend elements retain source-span IDs and parameter provenance in Go. Calculation errors are exposed as structured diagnostics and mapped back to the originating span for CLI and LSP consumers without sending source contents through the ABI.

.xal lowering and efficiency

The envelope frontend parses the original .xal bytes once and lowers the typed concepts directly to EngineDocumentSpec. The same parsed concept tree can supply diagnostics, LSP symbols, RAG rows, and engine input; those consumers do not serialize and parse an intermediate representation.

All-item groups select the generic adaptive-grid policy. Its candidate-column scan is linear in the number of slots, uses the resolved content aspect ratio and label reservation, and preserves stable source-order tie breaking. Frame metadata is lowered as ordinary invisible layout containers plus styled text cells, so its reserved strip and content offset use the same core allocator instead of renderer-specific coordinates.

V1-profile groups retain the V1 header-tag geometry: 32-pixel group icons, left-aligned labels, border-top alignment, frame-metadata clearance, and collision avoidance against preceding headers and group boundaries. Collision queries use bounded passes over narrow vertical buckets rather than scanning every prior element. Group SVG colors are normalized once and retained in a size- and entry-bounded cache.

ProjectConcept is a Go type alias of the closed EngineConcept vocabulary, so project analysis, LSP, RAG rows, and future engine lowering do not need another domain-name-to-concept conversion.

The ABI representation is designed for that reuse:

typed concept tree
  -> one pre-order flatten pass                 O(n)
  -> parent-indexed contiguous ABI records      O(n)
  -> Rust ID validation + child adjacency       O(n)
  -> recursive layout in stable source order
  -> line endpoint lookup by ID                 O(1) average per endpoint

This removes a JSON/map conversion and avoids rebuilding hierarchy in every output adapter. Routing currently evaluates generic obstacle candidates per line; dense diagrams therefore retain a line-by-obstacle cost and should be profiled separately from parsing and layout.

Reference benchmark

The repository includes opt-in benchmarks based on both complex-hybrid sample versions:

make build
CGO_ENABLED=1 go test \
  -tags 'xaligo_engine xaligo_exporter sqlite_fts5 sqlite_omit_load_extension' \
  ./test/integration -run '^$' \
  -bench '^BenchmarkComplexHybridV2(RenderSVGEndToEnd|FrontendLower)$' \
  -benchmem -benchtime=100x

A 2026-08-30 warm-cache reference run on Apple M2 (darwin/arm64) produced:

StageTime/opGo B/opGo allocs/op
V2 compatibility frontend lower0.314 ms340,1764,611
V2 complete SVG render3.21 ms2,334,1557,199

These rows include the V1-style adaptive item grid, frame metadata composition, group-header collision handling, catalog labels, tinted group assets, generic routing, and SVG embedding used by the V2 sample. Go’s B/op does not account for allocations made by Rust. Use the benchmark to detect regressions within a row; renderer and routing differences still make a V1/V2 wall-clock comparison unsuitable as a component benchmark.

Project intelligence remains a separate workflow. Initial RAG discovery indexes only Markdown under docs/. A .xal concept tree is analyzed only when an editor, agent, or future V2 frontend explicitly supplies that document; it is not added to the initial RAG corpus.

Compatibility

Existing callers that provide the original flat Direction, Gap, and Elements fields continue to work. A blank element concept is treated as a generic Item, and vertical or horizontal allocation retains the original fixed/flexible behavior. New callers can add hierarchy and typed parameters without changing the three use-case methods.