Efficient Integration of Map Controls in GUI Applications
How Carmenta Engine Maps are Updated
Carmenta Engine contains MapControls for common Graphical User Interface (GUI) frameworks. These help you integrate the map into your application. The map control is connected to a View that represents the map.
The Carmenta Engine map controls use an internal map update scheduler that integrates with the different ways of handling input and drawing in each GUI framework. Calls to update the View will be passed to the scheduler instead of being performed immediately. This article describes how the scheduler works and how you can change its settings in order to control how and when the map is updated.
If you update a View that is not connected to a MapControl, the call to View.Update will block until the map has been redrawn. The scheduler is only used when the View is connected to a MapControl.
When to Update the Map?
Ideally, you will only update the map when it is needed. If you are using a map control you will be using a mix of manual and automatic updates.
Manual Updates
When you for example change the geographical area shown or modify features, you want to see the effect of those changes. This is when you tell the View to update using one of the View.Update or MapControl.UpdateView methods.
You will find guidelines on when to use which method later in this article.
Automatic Updates
There are also many cases in which Carmenta Engine will call for an update automatically. Some examples: When you use TileLayer or DynamicAsync, updates will be requested when new data has been loaded. If you have animated attribute variables, they will request updates as long as they are running and are visible. Using the StandardTool and the other standard tools will request updates when performing kinetic scrolling or some zoom operations.
These automatic updates are only done if the view is connected to a map control.
Update Mode
Both manual and automatic update requests are handled based on the value of the MapControl.UpdateMode property.
The default and recommended mode is Scheduled. It is designed to:
Only update the map when it is needed. The automatic updates mentioned above and manual updates are the only sources of updates.
Prevent updates from occurring too often. MapControl.UpdateInterval is used to control the minimum interval between updates.
OnTimer is used to get regular updates at a set interval, regardless of whether something has actually changed in the map. It might feel natural to use this mode when you already know that you want updates once per second for example. However, applications with such requirements are usually driven by a simulator and a better option is to use Scheduled and make manual call to update when the simulator has ticked. This will make sure that the updates are never performed twice with the same simulator state.
The final mode is Immediate. This mode mostly remains for backwards compatibility reasons. In this mode updates will be performed without considering any frame rate limit. This can cause many more updates than necessary, starving input events and other UI work.
Internal Scheduler
As mentioned above, Carmenta Engine uses an internal scheduler to manage requested updates. This is necessary because Carmenta Engine performs updates on the GUI thread which in turn means that no other GUI related work can be performed while an update is being made. Examples of other GUI related work include updating other parts of the application, handling input, taking care of events and delegated work.
When an update request is made, the scheduler creates a timestamp which represents when the map wants to be updated. The call to update will return immediately - it will merely tell the scheduler to send an event to the map control when it decides that it is time for the update to be done. Different map controls have different logic for how this event is handled, but it will eventually result in performing the actual update of the map.
After sending the event to the map control, the scheduler calculates a timestamp in the future which it must wait until before it can send another request to a map control. This time is calculated by adding:
The length of time of the previous update. It anticipates that this is how much time Carmenta Engine will block the GUI thread while updating the map.
A GUI delay based on the time the previous update took, possibly increased by a minimum GUI delay that is always added. These delays can be modified using MapControl.GuiDelayPercentage and MapControl.GuiDelayMinimum.
Once an update is made, the scheduler checks how much time the update took and calculates a GUI delay. In cases where the update was quicker than before, the delay before the next update is decreased. If the update was slower, the delay before the next update is increased. This is done to compensate for the time Carmenta Engine blocks the GUI thread while a map is being rendered.
The scheduler includes time spent on the GUI thread while handling the two events View.Updating and View.Updated in the time an update takes.
Multiple Map Controls
If there are multiple map controls involved, updates will be scheduled to try and get a balance between updating all of them.
Note that while under heavy load, some GUI frameworks can cause a bias which means that some controls are updated more often than others. In the general case, updates should be distributed fairly between all map controls.
Using the API
Updating
You can call either View.Update or MapControl.UpdateView to update the map. The latter method is usually preferred since it is thread safe.
Choosing ViewUpdateKind
In most cases you should use the update method without the ViewUpdateKind parameter. This will make sure the update adheres to the MapControlUpdateMode that is used. Immediate should be used when you want an update as fast as possible while still respecting the internal scheduling mechanism. A typical use case is after you have performed some UI action that should directly change visualization, like enabling or disabling a layer.
In rare cases your application might need to perform an update directly and not continue with other code until that update has been completed. Use MapControl.UpdateView or View.Update together with Blocking to perform such an update. This will completely bypass the internal scheduler. If performed regularly, blocking updates will have a negative effect on the stability of the framerate and how often input is handled. You should therefore only use this update type when it is necessary. An example would be when you are about to save the map with Drawable.Save and want to make sure that any recent changes that have been made are included in the saved image.
Improving Performance
Creating an application with good user experience requires a balance between the rate at which the map is updated and the amount of input events that the application has time to handle.
Make the Map Faster
The first step when optimizing performance is always to make sure that your map configuration is as fast as it can be. If it takes one second to update the map your maximum framerate will never be higher than 1 frames per second (FPS), and that is without taking care of other things like input.
Carmenta Engine has a Profiler which can be used to measure the time spent in various parts of the configuration. There is also a performance test in Carmenta Explorer that can be used to measure the performance of a map configuration. You can also enable View.DisplayStatistics to see the framerate printed in the map's top left corner. Use these tools to find out the time it takes to update the map. Depending on your configuration and how the data is spread out, you will get varying performance depending on the scale, geographical area and the number of visible features. If the map is much slower in certain areas, you should start by focusing on understanding why and see if you can do something about it. After that you should try and improve the general performance of the map.
Do Less on the GUI Thread
The update scheduler will leave chunks of time for the application to perform other work on the GUI thread. This is mainly meant to be used to handle input and other event driven code.
However, the scheduler is designed to handle a relatively consistent workload on the GUI thread and it can't anticipate sporadic heavy usage of the GUI thread. Sporadic and heavy usage of the GUI thread can result in temporary situations where there is no time to handle input between updates. This is fine once in a while, but if the map constantly jumps between fast and slow updates the experience could get worse since the scheduler will be wrong most of the time. Keep in mind that the application can feel unresponsive both if updates are few but inputs are many, or vice versa. Keeping the time it takes to perform an update steady will result in a better user experience.
As mentioned earlier, you can use View.Updating and View.Updated to perform work that will be included in the calculated update time and therefore considered when updates are scheduled. Due to the way the scheduler works, only use these events for work that actually needs to be done each frame, rather than using it for work that happens very seldom.
Adjust Intervals
With the MapControlUpdateMode set and all the update calls in place, you can start to do performance profiling of the application. In the best case, the default settings will be enough.
Keep in mind that for the system to run at the desired framerate, the interval must be larger than the time it takes to perform an update plus the time for GUI delay.
Update Interval
MapControl.UpdateInterval is used for Scheduled and OnTimer. If you ask for more updates than the system can deliver, the system will still work since it adds the GUI delay time between updates. Be mindful about how often the map needs to be updated though, a reasonable value will make it easier for the scheduler to handle spikes in GUI thread usage from outside Carmenta Engine.
Tool Interval
Custom tools that implement IToolTimerSink get periodic callbacks on the GUI thread and this timer will keep firing without regards to how long it takes to update the map. To avoid this affecting the update scheduler you can either decrease the tool timer interval or move work that should be done before an update into the View.Updating event. For example, a tool that animates a camera along a path could trigger updates from the tool timer callback but use the View.Updating event to calculate new camera positions.
GUI Delay Percentage
MapControl.GuiDelayPercentage is used to calculate a GUI delay that is based on how long it takes to perform an update. When updates take longer the time left for the application to run code on the GUI thread will increase and vice versa.
GUI Delay Minimum
If you know that your application will do things that take a certain time between each update, you can set this value to a fixed minimum delay between updates. Generally, it should be enough to use MapControl.GuiDelayPercentage but if you have set the update interval to get a very high FPS and you have a very fast map configuration, then it could be a good idea to increase the value of this property so the application can handle more input events.
Best Practices
Many performance related problems when using Carmenta Engine are due to using the GUI thread too much. Here are a couple of tips to help you avoid this.
Use Update Attributes to Avoid taking the Configuration Lock
The Carmenta Engine threading model means that you have to use the Guard class when you modify objects that are connected to a View, such as when modifying Visualizer or Operator properties. Taking the guard means that the thread has to wait for other Carmenta Engine threads to finish their work, and when done on the GUI thread this can result in unpredictable delays that will interfere with the scheduler's process.
Rather than modifying such objects directly, consider controlling them using update attributes. View.UpdateAttributes can be modified without taking the configuration lock as long as you are on the GUI thread, and the update attributes can be used both in conditions and attribute variables.
Perform Heavy Work on a Background Thread
A common use case is to make some sort of calculation based on where the mouse cursor is in the map. If you perform this calculation directly in the mouse move event handler, it will be done on the GUI thread. As mentioned earlier, in cases where the system is struggling to update the map at the framerate you want, doing things that block the GUI thread for longer periods of time will affect the performance negatively.
For example, let's say that the map can be updated at 60 FPS and the calculation takes 100 ms. If you do the calculation in the event handler you will be limited to a theoretical 10 FPS, but you will probably get even less. To get a smooth user experience, it is better to let the map update as often as it can and let the event handler use a background thread to perform the calculation.
On the background thread, you can for example set up an entire operator chain with operators and datasets that are not connected to the View that is used in the MapControl. Your calculation can then do queries on this operator chain, while the GUI thread can focus on updating the map.