Coherent UI  2.5.3
A modern user interface library for games
 All Classes Namespaces Functions Variables Enumerations Enumerator Pages
View types

Coherent UI supports two types of views - buffered and on-demand.

Buffered Views

Buffered views are the default view type supported by Coherent UI. They are created by specifying

info.IsOnDemand = false;

in their ViewInfo at the time of their creation.

These views are completely automatic. Their rendering is performed in Coherent UI' according to optimal internal rules. When a frame of such view has been completed the OnDraw method of the ViewListener attached to it will be called in the stack frame of the Coherent::UI::ViewContext::FetchSurfaces method. All rendering of such views is lazy. If nothing visual has changed in the view no rendering will occur and hence the Coherent::UI::ViewListener::OnDraw callback won't be invoked.

When triggering events on such views the user is not guranteed that the change will be immediately visible in the next received frame. Internal optimizations buffer frames for these views and offer better work balancing and smooth animations, so changes in a view might be user-visible after more than one frame. This slight delay is hardly ever user-noticeble especially for view that show just animations, menus or in-game browsers.

The delay however might be a problem if the game runs with low frame-rates (under 25-30) or if perfect visual synchronization is required between events happening in the game frame and the UI. Such situations might be the nameplates of characters in an RPG where they must always be 'glued' to the game entities and never 'lag' behind them or a real-time strategy where health bars rendered by Coherent UI must always stay at a pixel-perfect relative position to every unit. In such cases the user should use on-demand views.

On-demand Views

On-Demand views offer a mechanism to synchronize the game frame and the events it generates with the UI frame. These views are rendered only on-demand by the user via a call to RequestFrame. Coherent UI guarantees that any events triggered prior to the call to Coherent::UI::View::RequestFrame will be processed in the next frame returned to the user via a call to Coherent::UI::View::FetchSurface. For on-demand views surfaces should be requested explicitly for every such view as a method of the Coherent::UI::View class itself. Coherent::UI::View::FetchSurface will block until the requested frame has been rendered and call Coherent::UI::ViewListener::OnDraw with the new surface. A non-blocking method Coherent::UI::View::PeekFrameReady is available to tell the user if the requested frame has been rendered.

On-Demand views require a little more effort from the user but are much more powerful by providing complete scheduling control.

Note
When requesting a frame for on-demand views it will be rendered even if there is no visual change in the surface.
If you use the blocking version of FetchSurface you should trigger it only after the view has been fully loaded - that is after ViewListener::OnFinishLoad with isMainFrame == true has been received. Otherwise a deadlock is possible where you wait for the rendered surface but Coherent UI needs resources (usually the page itself) to be read via the file handler.

Time control

On-Demand views allow You to control the way time passes in them. In this way your UI animations and JS timers will always advance as much as your application has advanced between two consecutive frames (or you can make time go faster/slower or pause it altoghther). To create a view where You control time you need to set ViewInfo::ControlTimeOnDemand to true and request new frames via the View::RequestFrame(double timeSinceArbitraryMoment) overload. Here the parameter timeSinceArbitraryMoment must be a monotonically increasing value in seconds. You can use the time since the application started or any other time point in the past.

Note
timeSinceArbitraryMoment is not a delta value between frames. It must be the time in seconds since a moment in the past. It must be monotonically increasing and you can't make time go backward. Time set this way controls all animations as well as JavaScript intervals and AJAX request timeouts in Your view. To get the 'application time' you can use the Date.appTime() method in JavaScript which returns the number of milliseconds since the epoch in application time, not 'real' time. You can still get the real computer time by simply creating a normal Date() object. The raw number of ticks from the epoch corrected by the application time is available via engine.ticks. Sometimes it might be useful to have Date() return the application time as well - for instance if you don't want to change pre-existing JavaScript code but you still want to be able to control time. This can be trivially achieved: just pass Date = Date.prototype.appTime as JavaScript code you want to be executed as soon as a view is created. This is done via the ViewContext::CreateView(const ViewInfo& info, const wchar_t* path, ViewListenerBase* listener, const wchar_t* scriptToExecute) overload. The last parameter is JavaScript code that will be executed when the view is created. Now even the default 'Date' object will work only in application time.

Best practices

Buffered views should be the default choice when no perfect visual synchronization is required between the game frame and the user interface. They are completely automatic and offer optimal load balance. Usually buffered views should be used for:

  • views that don't respond to triggers
  • views that only show animations
  • menus
  • hud
  • in-game browser New surfaces for buffered views are fetched via the Coherent::UI::ViewContext::FetchSurfaces method. No calls must be made to request new frames.

On-demand views are more powerful but require more bookkeeping. They should be used if the user must be sure that some events must be rendered in particular frames. Typical scenarios where on-demand views are a perfect fit are:

  • nameplates of characters
  • call-outs
  • health bars of units Frames for on-demand views must be explicitly requested and fetched per-view.

The best possible performance with on-demand views is gained when you structure your interaction with the view context as follows:

  • new frame starts
  • trigger all events that should be visible in this frame
  • draw the game frame
  • fetch the view and composite

You should always try to put as much computation between the frame requests and their fetches as possible because Coherent UI renders in multi-threaded way. Thus by drawing the game frame simultaneously with the UI you maximise the chances that the interface will be already rendered when you try to fetch it, leveraging modern multi-core systems.

Buffered views will never block the user frame. On-demand views block in Coherent::UI::View::FetchSurface until the requested frame is ready, Coherent::UI::View::PeekFrameReady however allows the user to monitor the completion of the rendering process.

Note
On-demand views are incompatible with software-rendered views.

On-demand views and intercepting URL requests

Currently intercepting URL requests and On-demand views should be used carefully together, since they might cause a deadlock in client application. While the client is blocked waiting for a surface from a on-demand view, the view might be blocked waiting for the client to intercept and allow some URL request. This can be avoided by either using a different thread for rendering the view (i.e. calling Coherent::UI::View::FetchSurface in your renderer thread and Coherent::UI::ViewContext::Update in your main thread) or by carefully designed main loop. Here the context is updated until the new frame is ready, so calling Coherent::UI::View::FetchSurface will not block.

void UpdateAndFetchView(
{
do
{
context->Update();
}
while (view->PeekFrameReady() == Coherent::UI::VE_FrameNotReady);
view->FetchSurface();
}