LuciadCPillar provides a Map as the central component for visualizing geospatial data. You can’t show the Map on screen instantly: you need to make a graphics environment available first. The graphics environment is provided by a UI framework of your choice. Because different UI frameworks have different threading models, the Map enforces some rules to help make sure that things happen on the right thread.

UI frameworks and their threading models

Many UI frameworks are entirely single-threaded: interacting with graphical components and rendering them happen on the same thread. If your framework falls into this category, all interaction with the Map and its renderer must happen on that thread.

Some frameworks, however, separate component interaction from component rendering. An example is Qt Quick with the threaded render loop enabled. In these frameworks, it’s important to do things on the appropriate thread.

Thread naming conventions

When component interaction and component rendering happen on distinct threads, we use these designations:

UI thread

The thread you use to interact with the GUI components, handling input events, or modifying component state, for example.

Render thread

The thread that renders GUI components: it reads or copies the state from the component when it’s safe to do so. The actual rendering happens in parallel with input event handling.

For frameworks that don’t make this distinction, both names refer to the single thread used by the framework.

What goes where?

The API offers guidance about the thread expected for the interaction of your code with Map. To get the renderer from a Map, use Map::getRenderer(). You must call this function, and any of the renderer’s functions, on the render thread.

You must call all other functions on the Map must on the UI thread. This also applies to the classes that make up the Map’s state, such as LayerList and Layer.

It’s impossible to explicitly communicate which threads are the UI and render threads, so the Map implicitly detects them. It assumes that the thread on which the Map object is created is the UI thread, and that the thread with the first call to getRenderer is the render thread. Any later function calls verify that they’re happening on the expected thread.

Synchronous functions

Most functions on the Map are synchronous. The effect or result is produced immediately in the current thread. You can call all these functions safely even if there is no renderer yet. This makes it easy to set up the Map as required, its size and layers for example, without having to worry about threads yet.

Asynchronous functions

Some functions on Map are asynchronous. These functions compute their result on a different thread, the render thread typically. Then, they provide that result through a callback passed to the function. The callback is always invoked on the UI thread, so that you can use the result to update the application state.

For this to work, you must first invoke the renderer on the render thread, so that the necessary computations can be performed. Then, you must invoke the Map on the UI thread, so that any callbacks can be called with the result. Usually, this just means that the UI framework’s event loop needs to do its job.

Camera

The camera API deserves a special mention in this article because it can be used on any thread. As a general rule, the map renderer is in charge of the camera. This has consequences when using the API. Depending on which thread you use to access the camera, the behavior is different:

In most cases, it is recommended to set and retrieve the camera on the render thread.

For more background on the camera API, you can read this article.