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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
