Skip to content

feat(core): js-parity global-state expression support - #4516

Open
NEKOYASAN wants to merge 33 commits into
maplibre:mainfrom
NEKOYASAN:feat/js-compat-global-state
Open

feat(core): js-parity global-state expression support#4516
NEKOYASAN wants to merge 33 commits into
maplibre:mainfrom
NEKOYASAN:feat/js-compat-global-state

Conversation

@NEKOYASAN

@NEKOYASAN NEKOYASAN commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

resolve #3302

Summary

  • add the global-state expression and the root state property, with the runtime API style::Style::setGlobalStateProperty / getGlobalState
  • track the state keys referenced by each expression so a change only re-evaluates and relayouts the layers and sources referencing a changed key (parity with gl-js getGlobalStateRefs)
  • align failed zoom-only property expression evaluation with GL JS: evaluation failures now use the property's style-spec default instead of value-initializing its type. This applies to all zoom-only expressions, not only global-state expressions
  • reject global-state updates until the style JSON has loaded, matching GL JS, and expose style::Style::isLoaded so platform bindings can distinguish this point from full resource loading
  • and platform apis

Verification

  • cmake --build build-macos-metal --target mbgl-test-runner and the style/expression suites, including the new GlobalStateExpression.*, Style.GlobalState*, Style.Visibility*, and Layer.SymbolLayerOverrides tests
  • Android: :MapLibreAndroid:testOpenglDebugUnitTest plus instrumentation tests on an emulator (RuntimeStyleTests, MapSnapshotterTest), arm64 native build
  • iOS: bazel test //platform/ios/test:ios_test --//:renderer=metal (MLNStyleTests, MLNExpressionTests)
  • Node: built the addon locally and ran the tape tests (test/js/global_state.test.js, map.test.js)

Note

The code in this PR was written with the help of Claude (Fable 5), under my instruction and review.

@github-actions github-actions Bot added build Related to build, configuration or CI/CD android iOS core Changes that affect the C++ core of MapLibre Native node labels Aug 24, 2026
@NEKOYASAN NEKOYASAN changed the title feat: js-parity global-state expression support feat(core): js-parity global-state expression support Aug 24, 2026
@NEKOYASAN
NEKOYASAN marked this pull request as ready for review August 24, 2026 15:01

@louwers louwers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked through it, looks quite good!

Let's port the render tests from MapLiibre GL JS. https://github.com/maplibre/maplibre-gl-js/tree/main/test/integration/render/tests/global-state

@NEKOYASAN

Copy link
Copy Markdown
Collaborator Author

@louwers thank you!
I ported the rendering test from GL JS!
I also fixed an issue with how unspecified (default) values were being handled.

image

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Bloaty Results 🐋

Compared to main

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +1.6% +2.83Mi  +1.9%  +676Ki    TOTAL

Full report: https://maplibre-native.s3.eu-central-1.amazonaws.com/bloaty-results/pr-4516-compared-to-main.txt

Compared to d387090 (legacy)

    FILE SIZE        VM SIZE    
 --------------  -------------- 
   +58% +67.1Mi  +487% +29.1Mi    TOTAL

Full report: https://maplibre-native.s3.eu-central-1.amazonaws.com/bloaty-results/pr-4516-compared-to-legacy.txt

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results ⚡

Benchmark                                                          Time             CPU      Time Old      Time New       CPU Old       CPU New
-----------------------------------------------------------------------------------------------------------------------------------------------
OVERALL_GEOMEAN                                                 +0.0025         +0.0027             0             0             0             0

Full report: https://maplibre-native.s3.eu-central-1.amazonaws.com/benchmark-results/pr-4516-compared-to-main.txt

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Bloaty Results (iOS) 🐋

Compared to main

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.6% +99.0Ki  +0.6% +96.0Ki    TOTAL

Full report: https://maplibre-native.s3.eu-central-1.amazonaws.com/bloaty-results-ios/pr-4516-compared-to-main.txt

@NEKOYASAN
NEKOYASAN requested a review from louwers August 25, 2026 07:45
@louwers

louwers commented Aug 25, 2026

Copy link
Copy Markdown
Member

Nice!

@NEKOYASAN

Copy link
Copy Markdown
Collaborator Author

It looks like CI was failing because I had copied v8.json directly from @maplibre/maplibre-gl-style-spec. I’ve now made a few Native-specific adjustments to it.
I’m not entirely sure this is the ideal approach, but I found several other PRs that handled similar style-spec synchronization issues this way, so I followed that precedent for now.

@NEKOYASAN

Copy link
Copy Markdown
Collaborator Author

I'm truly sorry that this turned into such a huge PR...

Based on the comments below, I have added tests and confirmed that everything is working as expected.
maplibre/maplibre-style-spec#886 (comment)

@johncarmack1984 johncarmack1984 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saw Bart asking for reviews on this in the newsletter thread, thought I'd pitch in.

Read the core and platform halves against GL JS's implementation (style.ts, the spec's global_state.ts and visibility.ts). The semantics line up as far as I can trace. Null when unset, defaults from state, null resets to the default, re-evaluation scoped to the keys that changed. Filters in the query paths, format overrides and the color ramps too.

For anyone looking at the two red checks: those are the Android render-test legs from a workflow_run on main at be3f03b, which failed once and passed on the rerun. Every run on this PR's own head (fc32f1f) is green.

Looked into @HarelM's note about porting the rest of the GL JS global-state tests: 59 of the 69 are in here. The other 10 are sky, projection, and the two *-layer-opacity properties, none of which exist in native yet. Should stubs or // TODO comments go in to keep parity, or something else?

Comment thread vendor/expected-lite
Comment thread src/mln/tile/tile.hpp Outdated
Comment thread include/mln/renderer/query.hpp Outdated
Comment thread src/mln/renderer/data_driven_property_evaluator.hpp
Comment thread src/mln/style/style_impl.cpp
NEKOYASAN and others added 23 commits September 7, 2026 00:50
@NEKOYASAN
NEKOYASAN force-pushed the feat/js-compat-global-state branch from fc32f1f to b8969dd Compare September 6, 2026 15:50
@NEKOYASAN

Copy link
Copy Markdown
Collaborator Author

@johncarmack1984 Thank you for your review!

For anyone looking at the two red checks: those are the Android render-test legs from a workflow_run on main at be3f03b, which failed once and passed on the rerun. Every run on this PR's own head (fc32f1f) is green.

I rebased upstream/main, and all 50 checks passed.

The other 10 are sky, projection, and the two *-layer-opacity properties, none of which exist in native yet. Should stubs or // TODO comments go in to keep parity, or something else?

I think it is fine to exclude these for now. I could not find any precedents in the Native codebase where stubs or TODO comments were added for Mapbox GL JS rendering test features that are not yet supported on the Native side. so I believe it would be better to port them together when implementing those features themselves.

@johncarmack1984 johncarmack1984 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the four new commits against the threads. Submodule is back on main's commit. Tile::setLayers carries the state now, and setGlobalState went with it. The query paths hand the state over next to the options instead of on the public structs. The pre-load call throws before the style is marked mutated, and isLoaded() is in the changelog. Range-diff of the rebased series against what I read last week is all =, so nothing else moved. All 49 checks green on the head. Good from me (with the caveat that I am just a citizen-contributor, not a maintainer!). Hope this was helpful! :)

Comment on lines +548 to +551
- (void)setGlobalStateValue:(id)value forProperty:(NSString *)propertyName {
MLNLogDebug(@"Setting global state property: %@", propertyName);
self.rawStyle->setGlobalStateProperty(propertyName.UTF8String, MLNValueFromJSONObject(value));
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One iOS side effect of the new throw: MLNMapSnapshotter.style hands out an MLNStyle before the style has loaded, and this call goes straight into core. A pre-load call used to drop the value; now it terminates the process, since nothing on the ObjC side catches it. The sibling methods in this file wrap the core call in try/catch and raise an NSException. Same wrapper here would match the Kotlin IllegalStateException. Not blocking.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your review! your review is very helpful to me!

I was satisfied after fixing the Android side... I have now also fixed the iOS side to throw an NSInternalInconsistencyException.

I also wanted to review the globe view PR in appreciation of your review, but it turned out to be quite difficult... ;_;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha yeah that one is HUGE, but thanks for taking a crack at it :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

android build Related to build, configuration or CI/CD core Changes that affect the C++ core of MapLibre Native iOS js-parity node WebGPU

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support global map state

3 participants