Introduction

CPillar provides a Map as the central component for visualizing geospatial data. The Map cannot directly be shown on screen: it relies on a graphics environment to be available, which is provided by a UI framework of the user’s choice. Because different UI frameworks have different threading models, Map enforces some rules to help ensure that things are done on the expected thread.

UI frameworks

Many UI frameworks are entirely single threaded: interacting with graphical components and rendering them happen on the same thread. If your framework falls in this category, things are easy: all interaction with Map and its renderer must be done 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 is important to do things on the appropriate thread.

Threads

When component interaction and component rendering happen on different threads, we use the following designations:

UI thread

This is the thread on which you interact with the GUI components: handling input events, modifying component state and so on.

Render thread

This is the thread on which GUI components get rendered: state from the component is read or copied when it is safe to do so and the actual rendering happens in parallel with input event handling.

For frameworks that do not make this distinction, both names refer to the single thread used by the framework.

What goes where?

To provide guidance on which thread your code is expected to interact with Map, its API is split up. From Map, you can get its renderer using Map::getRenderer(). Calling this function, as well as any of the renderer’s functions, must be done on the render thread. All other functions on Map must be called on the UI thread.

It is impossible to explicitly communicate which threads are the UI and render threads, so Map implicitly detects them. The thread on which the Map object is created is assumed to be the UI thread, while the thread on which the first call to getRenderer happens is assumed to be the render thread. Any subsequent function calls verify that they are done on the expected thread.

Synchronous functions

Most of the functions on Map are synchronous. The effect or result is produced immediately in the current thread. All these functions can be called safely even if there is no renderer yet. This makes it easy to set up the Map as required (size, layers and such) without having to worry about threads just yet.

Asynchronous functions

Some functions on Map are asynchronous. These functions compute their result on a different thread (usually, the render thread) and provide that result through a callback passed to the function. The callback is always invoked on the UI thread, allowing you to use the result for updating the application state.

For this to work, first the renderer needs to be invoked on the render thread, so the necessary computations can be performed, and then the Map needs to be invoked 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 be doing its job.

More details on the integration required to make this work can be found in the UI Framework Integration section.