Why GaiaX Chose Flexbox and Stretch: A High‑Performance Layout Engine Deep Dive

This article explores GaiaX's cross‑platform layout solution, detailing the performance tests that validated Flexbox, the advantages of the Rust‑based Stretch library, and the engineering fixes for iOS crashes, aspect‑ratio handling, and multithreading issues.

Alibaba Terminal Technology
Alibaba Terminal Technology
Alibaba Terminal Technology
Why GaiaX Chose Flexbox and Stretch: A High‑Performance Layout Engine Deep Dive

GaiaX is a cross‑platform template engine widely used within Alibaba's entertainment division, offering performance, stability, and ease of use. The open‑source repository is available at https://github.com/alibaba/GaiaX .

GaiaX Layout Scheme – Flexbox

Alibaba's entertainment services run on phones, pads, OTT, Macs, car‑head units, and IoT screens, requiring a responsive layout across many device sizes. Browsers solve this with Flexbox, and GaiaX adopts the same approach.

Native frame layout provides the best performance on mobile devices. To verify Flexbox's real‑world performance, GaiaX designed single‑layer and multi‑layer nesting test cases and measured execution time in milliseconds.

The results dispelled performance concerns, and because Flexbox follows W3C standards, has a rich ecosystem, and is easy to learn, the team selected it as GaiaX's layout technology.

Flexbox High‑Performance Analysis – Stretch

Why Choose Stretch as the Layout Engine

Among community‑supported Flexbox parsers, Facebook’s Yoga is mainstream, but Stretch offers several advantages:

Small package size

Support for multi‑threaded local computation

Cross‑platform capability (iOS, Android, JS, etc.)

Implemented in Rust, delivering high performance and low memory usage

These benefits stem from Rust’s language features:

Safer memory management : Rust prevents null pointers, buffer overflows, and memory leaks at compile time.

Better runtime performance : Rust does not rely on a garbage collector, which improves execution speed compared to Java or C#.

Native multithreading support : Ownership and borrowing guarantee data‑race‑free concurrency.

Benchmarks show Rust’s performance can match or exceed C++ in many scenarios.

Stretch Introduction

Before diving into Stretch’s core layout logic, it is useful to review basic Flexbox concepts:

A Flex container holds Flex items, which are its direct children.

The container defines two axes: the horizontal main axis and the vertical cross axis.

Items are laid out along the main axis by default; the space they occupy on the main axis is the main size, and on the cross axis is the cross size.

Flexbox layout parsing processes container and item dimensions, direction, alignment, ratios, and absolute positioning.

Stretch Implementation Principle

Flexbox Layout Parsing Main Chain

Core Algorithm

Stretch Key Analysis

Stretch’s layout algorithm consists of nine stages; the article focuses on three critical ones:

Determining the flex‑basis of each item

Computing the main‑axis size of each item

Computing the cross‑axis size of each item

Determining Flex‑Basis

The algorithm builds a collection of flex items, iterates over them, and assigns a flex_basis value based on existing values, stretch alignment, or aspect‑ratio calculations.

Determining Main‑Axis Size

After flex_basis is set, the engine calculates the target_main_size by considering used space, remaining space, and the flex_grow/flex_shrink factors. The growth or shrinkage is proportional to these factors.

if growing {
    for target in &mut unfrozen {
        let child = target;
        if free_space.is_normal() && sum_flex_grow > 0.0 {
            let grow_after = child.flex_basis + free_space * (self.nodes[child.node].style.flex_grow / sum_flex_grow);
            child.target_size.set_main(dir, grow_after);
        }
    }
}

The shrink path mirrors the growth logic, using flex_shrink to compute a proportional reduction.

if shrinking && sum_flex_shrink > 0.0 {
    let sum_scaled_shrink_factor: f32 = unfrozen
        .iter()
        .map(|child: &&mut FlexItem| {
            let child_style: Style = self.nodes[child.node].style;
            child.inner_flex_basis * child_style.flex_shrink
        })
        .sum();
    for target in &mut unfrozen {
        let child = target;
        let scaled_shrink_factor = child.inner_flex_basis * self.nodes[child.node].style.flex_shrink;
        if free_space.is_normal() && sum_scaled_shrink_factor > 0.0 {
            let shrink_after = child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor);
            child.target_size.set_main(dir, shrink_after)
        }
    }
}

Determining Cross‑Axis Size

Cross‑axis calculation involves three sub‑steps: estimating each item's cross size, finding the maximum cross size per line, and adjusting dimensions based on aspect‑ratio, align‑self: stretch, and other properties.

if is_row && child_style.aspect_ratio.is_defined() && node_size.height.is_defined() && child_style.flex_shrink > 0.0 {
    let final_cross = child.hypothetical_inner_size.cross(dir).maybe_min(node_size.height);
    if !child_style.size.width.is_defined() && !child_style.min_size.width.is_defined() && !child_style.max_size.width.is_defined() {
        // fix: aspect_ratio_height_as_flex_basis
        let desire_height = child.target_size.width / child_style.aspect_ratio.or_else(0.0);
        desire_height
    } else if !child_style.size.width.is_defined() && child_style.min_size.width.is_defined() && !child_style.max_size.width.is_defined() {
        // fix: aspect_ratio_flex_shrink_2
        let desire_height = child.target_size.width / child_style.aspect_ratio.or_else(0.0);
        final_cross.maybe_min(Number::Defined(desire_height))
    } else if child_style.size.width.is_defined() {
        // fix: aspect_ratio_width_height_flex_grow_row
        let desire_height = child.target_size.width / child_style.aspect_ratio.or_else(0.0);
        desire_height
    } else {
        final_cross
    }
} else {
    let final_cross = child.hypothetical_inner_size.cross(dir);
    final_cross
}

Problems Encountered and New Features Added

iOS 32‑bit crash due to __modsi3 symbol conflict

Android GC crash

Multithreaded layout computation crashes

Aspect‑ratio attribute redefinition issues

Hidden bugs in deep Flex nesting (flex‑shrink/flex‑grow)

iOS 32‑Bit Crash

Root Cause

Stretch is compiled as a static Rust library for iOS armv7/armv7s. The __modsi3 macro expands to a division‑by‑zero operation when the divisor is zero, causing a crash.

#[maybe_use_optimized_c_shim]
pub extern "C" fn __modsi3(a: i32, b: i32) -> i32 {
    a.mod_(b)
}

trait Mod: Int {
    /// Returns `a % b`
    fn mod_(self, other: Self) -> Self {
        let s = other >> (Self::BITS - 1);
        let b = (other ^ s).wrapping_sub(s);
        let s = self >> (Self::BITS - 1);
        let a = (self ^ s).wrapping_sub(s);
        let r = a.unsigned().aborting_rem(b.unsigned());
        (Self::from_unsigned(r) ^ s) - s
    }
}

Solution

The team renamed the conflicting symbols in the static library and added extensive unit tests to verify stability.

Aspect‑Ratio Issues

Root Cause

Stretch treated aspect‑ratio based on the cross axis, requiring the flex item to be inside a container and making the ratio direction‑dependent, which confused developers.

Solution

The definition was revised to always represent width/height, independent of container direction. New rules:

aspect‑ratio = width / height

If the item has a defined width, compute height from the ratio.

If the item has a defined height, compute width from the ratio.

The ratio no longer depends on the container’s layout direction.

Corresponding layout calculations were updated, and comprehensive tests were added.

// fix: aspect_ratio_both_dimensions_defined_column
fn get_aspect_ratio_size(child_style: &Style, target_size: Size<Number>) -> Size<Number> {
    return Size {
        width: Forest::get_aspect_ratio_width(child_style, target_size),
        height: Forest::get_aspect_ratio_height(child_style, target_size),
    };
}

fn get_aspect_ratio_height(child_style: &Style, target_size: Size<Number>) -> Number {
    if target_size.width.is_defined() && child_style.aspect_ratio.is_defined() {
        let width = target_size.width.or_else(0.0);
        let aspect_ratio = child_style.aspect_ratio.or_else(0.0);
        return Number::Defined(width / aspect_ratio);
    }
    return target_size.height;
}

Thread‑Safety Problems

Root Cause

Stretch’s internal data structures (maps and arrays) are not thread‑safe, leading to crashes when nodes are added or removed concurrently.

pub fn add_child(&mut self, node: Node, child: Node) -> Result<(), Error> {
    let node_id = self.find_node(node)?;
    let child_id = self.find_node(child)?;
    self.forest.add_child(node_id, child_id);
    Ok(())
}

Solution

A singleton wrapper (GXStretch) was introduced, with a semaphore protecting all node operations, and style pointers are updated safely.

// GXStretch singleton
static GXStretch *stretch = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ stretch = [[GXStretch alloc] init]; });
return stretch;

- (void)addChild:(void *)child forNode:(void *)node {
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    stretch_node_add_child(_stretchptr, node, child);
    dispatch_semaphore_signal(_semaphore);
}

Verification

Extensive unit tests were added to cover the new features and bug fixes. Example for aspect‑ratio handling:

#[test]
fn aspect_ratio_cross_defined() {
    let mut stretch = Stretch::new();
    let root = stretch.new_node(Style { size: Size { width: Dimension::Points(100.0), height: Dimension::Points(100.0) }, ..Default::default() }, &[]).unwrap();
    let child = stretch.new_node(Style { size: Size { width: Dimension::Points(50.0), ..Default::default() }, aspect_ratio: Number::Defined(1.0), ..Default::default() }, &[]).unwrap();
    stretch.add_child(root, child).unwrap();
    stretch.compute_layout(root, Size { width: Number::Defined(375.0), height: Number::Defined(1000.0) }).unwrap();
    assert_eq!(stretch.layout(child).unwrap().size.width, 50.0);
    assert_eq!(stretch.layout(child).unwrap().size.height, 50.0);
}

Thanks to Rust’s safety guarantees, thorough testing, and the ownership model, the modified Stretch library now provides reliable layout computation for GaiaX.

Conclusion and Outlook

The original Stretch project is no longer maintained, so the GaiaX team forked the codebase and added many new features. This deep dive not only gave the team a comprehensive understanding of Stretch but also extensive experience with Rust. By open‑sourcing their work, they invite the community to contribute and help meet the growing cross‑platform dynamic UI demands of Alibaba’s entertainment business.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RustflexboxLayout EngineStretch
Alibaba Terminal Technology
Written by

Alibaba Terminal Technology

Official public account of Alibaba Terminal

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.