Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@

### ? - ?

##### Breaking Changes :mega:

- `Tileset::updateViewGroupOffline`, `Tileset::updateViewGroup`, and `Tileset::loadTiles` no longer call `AsyncSystem::dispatchMainThreadTasks`. Client applications must call that function themselves from their main thread.

##### Additions :tada:

- Added a constructor overload for `Cesium3DTilesSelection::ITwinCesiumCuratedContentLoaderFactory` to override the iTwin Cesium Curated Content base URL. This makes it possible to connect to alternate servers (e.g., staging, QA, mock servers).

##### Fixes :wrench:

- `Cesium3DTilesetSelection::Tileset::getRootTileAvailableEvent` will now resolve even when a `Tileset` is constructed with invalid source parameters, instead of hanging indefinitely.
- Fixed compilation error with MSVC when using custom `CesiumITwinClient::PagedList` types.
- Ability to override the iTwin Cesium Curated Content base URL used by `iTwinCesiumCuratedContentLoaderFactory` to make it possible to connect to alternate servers (e.g., staging, QA, mock servers).

### v0.58.0 - 2026-03-02

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,6 @@ class CESIUM3DTILESSELECTION_API Tileset final {
* In order to minimize tile load latency, this method should be called
* frequently, such as once per render frame. It will return quickly when
* there is no work to do.
*
* This method also calls
* {@link CesiumAsync::AsyncSystem::dispatchMainThreadTasks} on the tileset's
* {@link getAsyncSystem}.
*/
void loadTiles();

Expand Down
6 changes: 0 additions & 6 deletions Cesium3DTilesSelection/src/Tileset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,6 @@ const ViewUpdateResult& Tileset::updateViewGroup(
_options.enableFrustumCulling && !_options.enableLodTransitionPeriod;
_options.enableFogCulling =
_options.enableFogCulling && !_options.enableLodTransitionPeriod;

this->_asyncSystem.dispatchMainThreadTasks();

ViewUpdateResult& result = viewGroup.getViewUpdateResult();

Tile* pRootTile = this->_pTilesetContentManager->getRootTile();
Expand Down Expand Up @@ -444,9 +441,6 @@ const ViewUpdateResult& Tileset::updateViewGroup(

void Tileset::loadTiles() {
CESIUM_TRACE("Tileset::loadTiles");

this->_asyncSystem.dispatchMainThreadTasks();

Tile* pRootTile = this->_pTilesetContentManager->getRootTile();
if (!pRootTile) {
// If the root tile is marked as ready, but doesn't actually exist, then
Expand Down
1 change: 1 addition & 0 deletions Cesium3DTilesSelection/test/TestTileLoadRequester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ TEST_CASE("TileLoadRequester") {

// Request this tile for main thread loading and verify it happens.
requester.setMainThreadQueue({pToLoad});
externals.asyncSystem.dispatchMainThreadTasks();
pTileset->loadTiles();
CHECK(pToLoad->getState() == TileLoadState::Done);
}
Expand Down
9 changes: 9 additions & 0 deletions Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ TEST_CASE("Tileset height queries") {
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});

while (!future.isReady()) {
externals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
}

Expand Down Expand Up @@ -104,6 +105,7 @@ TEST_CASE("Tileset height queries") {
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});

while (!future.isReady()) {
externals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
}

Expand Down Expand Up @@ -138,6 +140,7 @@ TEST_CASE("Tileset height queries") {
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});

while (!future.isReady()) {
externals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
}

Expand Down Expand Up @@ -177,6 +180,7 @@ TEST_CASE("Tileset height queries") {
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});

while (!future.isReady()) {
externals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
}

Expand Down Expand Up @@ -212,6 +216,7 @@ TEST_CASE("Tileset height queries") {
{Cartographic::fromDegrees(-75.612559, 40.042183, 0.0)});

while (!future.isReady()) {
externals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
}

Expand All @@ -231,6 +236,7 @@ TEST_CASE("Tileset height queries") {
{Cartographic::fromDegrees(-75.612559, 40.042183, 0.0)});

while (!future.isReady()) {
externals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
}

Expand All @@ -250,6 +256,7 @@ TEST_CASE("Tileset height queries") {
{Cartographic::fromDegrees(-75.612559, 40.042183, 1.0)});

while (!future.isReady()) {
externals.asyncSystem.dispatchMainThreadTasks();
pTileset->loadTiles();
}

Expand Down Expand Up @@ -292,6 +299,7 @@ TEST_CASE("Tileset height queries") {
{Cartographic::fromDegrees(10.0, 45.0, 0.0)});

while (!future.isReady()) {
externals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
}

Expand Down Expand Up @@ -330,6 +338,7 @@ TEST_CASE("Tileset height queries") {
tileset.sampleHeightMostDetailed({samplePosition});

while (!future.isReady()) {
externals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
}

Expand Down
36 changes: 34 additions & 2 deletions Cesium3DTilesSelection/test/TestTilesetSelectionAlgorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ static void initializeTileset(Tileset& tileset) {
horizontalFieldOfView,
verticalFieldOfView,
Ellipsoid::WGS84);

tileset.getExternals().asyncSystem.dispatchMainThreadTasks();
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tileset.getExternals().asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
}

Expand Down Expand Up @@ -219,9 +220,11 @@ TEST_CASE("Test replace refinement for render") {
// Check 1st and 2nd frame. Root should meet sse and render. No transitions
// are expected here
for (int frame = 0; frame < 2; ++frame) {
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result = tileset.updateViewGroup(
tileset.getDefaultViewGroup(),
{zoomOutViewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// Check tile state. Ensure root meet sse
Expand Down Expand Up @@ -281,8 +284,10 @@ TEST_CASE("Test replace refinement for render") {
// 1st frame. Root doesn't meet sse, so it goes to children. But because
// children haven't started loading, root should be rendered.
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// Check tile state. Ensure root doesn't meet sse, but children does.
Expand All @@ -309,8 +314,10 @@ TEST_CASE("Test replace refinement for render") {
// 2nd frame. Because children receive failed response, so they will be
// rendered as empty tiles.
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// Check tile state. Ensure root doesn't meet sse, but children does
Expand Down Expand Up @@ -354,9 +361,11 @@ TEST_CASE("Test replace refinement for render") {
// 1st frame. Root doesn't meet sse, but none of the children finish
// loading. So we will render root
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result = tileset.updateViewGroup(
tileset.getDefaultViewGroup(),
{zoomInViewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// check tiles status. All the children should have loading status
Expand Down Expand Up @@ -388,9 +397,11 @@ TEST_CASE("Test replace refinement for render") {
// 2nd frame. All the children finish loading, so they are ready to be
// rendered (except ll.b3dm tile since it doesn't meet sse)
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result = tileset.updateViewGroup(
tileset.getDefaultViewGroup(),
{zoomInViewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// check tiles status. All the children should have loading status
Expand Down Expand Up @@ -448,6 +459,7 @@ TEST_CASE("Test replace refinement for render") {
ViewUpdateResult result = tileset.updateViewGroup(
tileset.getDefaultViewGroup(),
{zoomOutViewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// check tiles status. All the children should have loading status
Expand Down Expand Up @@ -495,8 +507,10 @@ TEST_CASE("Test replace refinement for render") {
// none of the children are loaded, root will be rendered instead and
// children transition from unloaded to loading in the mean time
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// Check tile state. Ensure root doesn't meet sse, but children does
Expand All @@ -522,8 +536,10 @@ TEST_CASE("Test replace refinement for render") {
// 2nd frame. Children are finished loading and ready to be rendered. Root
// shouldn't be rendered in this frame
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// check tile states
Expand Down Expand Up @@ -620,8 +636,10 @@ TEST_CASE("Test additive refinement") {
// 1st frame. Root, its child, and its four grandchildren will all be
// rendered because they meet SSE, even though they're not loaded yet.
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

const std::vector<Tile::ConstPointer>& ttr =
Expand Down Expand Up @@ -656,8 +674,10 @@ TEST_CASE("Test additive refinement") {

// 2nd frame
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

const std::vector<Tile::ConstPointer>& ttr =
Expand Down Expand Up @@ -715,6 +735,7 @@ TEST_CASE("Test additive refinement") {
{
ViewUpdateResult result =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

REQUIRE(result.tilesToRenderThisFrame.size() == 8);
Expand Down Expand Up @@ -786,8 +807,10 @@ TEST_CASE("Render any tiles even when one of children can't be rendered for "
// 1st frame. Root doesn't meet sse, so load children. But they are
// non-renderable, so render root only
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

for (const Tile& child : root->getChildren()) {
Expand All @@ -805,8 +828,10 @@ TEST_CASE("Render any tiles even when one of children can't be rendered for "
// 2nd frame. Root doesn't meet sse, so load children. Even one of the
// children is failed, render all of them even there is a hole
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

REQUIRE(root->isRenderable());
Expand Down Expand Up @@ -904,9 +929,11 @@ TEST_CASE("Test multiple frustums") {

// frame 1
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result = tileset.updateViewGroup(
tileset.getDefaultViewGroup(),
{viewState, zoomOutViewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// Check tile state. Ensure root meets sse for only the zoomed out
Expand All @@ -926,9 +953,11 @@ TEST_CASE("Test multiple frustums") {

// frame 2
{
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
ViewUpdateResult result = tileset.updateViewGroup(
tileset.getDefaultViewGroup(),
{viewState, zoomOutViewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// Check tile state. Ensure root meets sse for only the zoomed out
Expand Down Expand Up @@ -996,8 +1025,8 @@ TEST_CASE("Test multiple frustums") {
ViewUpdateResult result = tileset.updateViewGroup(
tileset.getDefaultViewGroup(),
{zoomInViewState1, zoomInViewState2});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// check result
// The grand child and the second child are the only ones rendered.
// The third and fourth children of the root are culled.
Expand Down Expand Up @@ -1288,6 +1317,7 @@ TEST_CASE("Makes metadata available on external tilesets") {
tileset.updateViewGroup(
tileset.getDefaultViewGroup(),
{zoomToTileViewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
pExternalContent = pExternal->getContent().getExternalContent();
}
Expand Down Expand Up @@ -1745,6 +1775,7 @@ TEST_CASE("Additive-refined tiles are added to the tilesFadingOut array") {
tileset.computeLoadProgress() < 100.0f) {
updateResult =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {viewState});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();
}

Expand All @@ -1765,6 +1796,7 @@ TEST_CASE("Additive-refined tiles are added to the tilesFadingOut array") {
Ellipsoid::WGS84);
updateResult =
tileset.updateViewGroup(tileset.getDefaultViewGroup(), {zoomedOut});
tilesetExternals.asyncSystem.dispatchMainThreadTasks();
tileset.loadTiles();

// Only the root tile (plus the tileset.json) is visible now, and the other
Expand Down
2 changes: 1 addition & 1 deletion doc/topics/async-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ In practice, though, we almost never call `wait` or `waitInMainThread`. After al

A continuation function is a callback - usually a lambda - that is invoked when the `Future` resolves. The name comes from the idea that the execution "continues" with that function after the async operation. Continuations are registered with the `then...` methods on the `Future`. Different `then` functions are used to control _in what thread_ the callback is invoked:

* [thenInMainThread](@ref CesiumAsync::Future::thenInMainThread): The continuation function is invoked in the "main" thread. Many applications have a clear idea of which thread they consider the main one, but in Cesium Native, "main thread" very specifically means the thread that called `waitInMainThread` or [dispatchMainThreadTasks](@ref CesiumAsync::AsyncSystem::dispatchMainThreadTasks) on the `AsyncSystem`. This need not be the same as the thread your application considers to be the main one.
* [thenInMainThread](@ref CesiumAsync::Future::thenInMainThread): The continuation function is invoked in the "main" thread. Many applications have a clear idea of which thread they consider the main one, but in Cesium Native, "main thread" very specifically means the thread that called `waitInMainThread` or [dispatchMainThreadTasks](@ref CesiumAsync::AsyncSystem::dispatchMainThreadTasks) on the `AsyncSystem`. This need not be the same as the thread your application considers to be the main one; however, your application must call `dispatchMainThreadTasks` periodically from a unique thread in order for main thread continuation functions to run at all.
* [thenInWorkerThread](@ref CesiumAsync::Future::thenInWorkerThread): The continuation function is invoked in a background worker thread by calling [startTask](@ref CesiumAsync::ITaskProcessor::startTask) on the `ITaskProcessor` instance with which the `AsyncSystem` was constructed.
* [thenInThreadPool](@ref CesiumAsync::Future::thenInThreadPool): The continuation function is invoked in a specified [ThreadPool](@ref CesiumAsync::ThreadPool). This is most commonly used with a `ThreadPool` containing just a single thread, in order to delegate certain tasks (such as database access) to a dedicated thread. In other cases, `thenInWorkerThread` is probably a better choice.
* [thenImmediately](@ref CesiumAsync::Future::thenImmediately): The continuation function is invoked immediately in whatever thread happened to resolve the `Future`. This is only appropriate for work that completes very quickly and is safe to run in any thread.
Expand Down
Loading