| Tilt Five NDK |

Getting Started : C++

# Files

The following files need to be included in your project

File(s) | Purpose
--- | ---
.hpp & .h files from the `include` directory | C and C++ header files for the API
One of the libraries from `lib` directory | The Tilt Five NDK support library

# Using the Glasses

## Service Connection

Most of the operations performed with the glasses are not done directly by the client, but are performed by the Tilt Five™ service on behalf of the client.

The connection to the service is not guaranteed to always be available. Scenarios where the service is unavailable include:

- During client startup there can be a small delay before the connection is established.
- The service is not installed (e.g. User hasn't installed the Tilt Five™ drivers).
- The service may have crashed or been stopped by a user. As such, all queries should anticipate a response of tiltfive::Error::kNoService and retry appropriately (assuming it's a transient condition).

A trivial example of handling this is shown below:

253// Convenience function to repeatedly call another function if it returns 'service unavailable'
254 template <typename T>
255 auto waitForService(Client& client, const std::function<tiltfive::Result<T>(Client& client)>& func)
256 -> tiltfive::Result<T> {
257
258 bool waitingForService = false;
259 for (;;) {
260 auto result = func(client);
261 if (result) {
262 return result;
263 } else if (result.error() != tiltfive::Error::kNoService) {
264 return result.error();
265 }
266
267 std::cout << (waitingForService ? "." : "Waiting for service...") << std::flush;
268 waitingForService = true;
269 std::this_thread::sleep_for(100_ms);
270 }
271}
tiltfive::Error::kNoService
@ kNoService
Service isn't connected.
std::cout
ostream cout
std::this_thread::sleep_for
void sleep_for(const chrono::duration< _Rep, _Period > &__rtime)
tiltfive::Result
Templated return type with support for error conditions.
Definition: result.hpp:37
std::shared_ptr< tiltfive::Client >
314 result = waitForService<void>(*client, printServiceVersion);
315 if (!result) {
316 std::cerr << "Failed to get service version : " << result << std::endl;
317 std::exit(EXIT_FAILURE);
318 }
std::cerr
ostream cerr
std::endl
basic_ostream< _CharT, _Traits > & endl(basic_ostream< _CharT, _Traits > &__os)
229 auto printServiceVersion(Client& client) -> tiltfive::Result<void> {
230 auto result = client->getServiceVersion();
231 if (!result) {
232 return result.error();
233 }
234
235 std::cout << "Service version : " << result << std::endl;
236 return tiltfive::kSuccess;
237}
tiltfive::kSuccess
static constexpr success_t kSuccess
Indicates 'success' for a Result<void> function.
Definition: result.hpp:324
tiltfive::Result< void >
Specialization of tiltfive::Result for functions with 'no return'.
Definition: result.hpp:199

## Result Wrapper

To avoid using exceptions, most C++ functions return the tiltfive::Result type.
This class wraps the 'real' return type and adds additional error conditions. tiltfive::Result is truthy, so you should first check if the result is valid before using it, after that you dereference the tiltfive::Result to get the 'real' value.

An example of this is shown below:

auto result = fooCall();
if (!result) {
doSomethingWithTheError(result.error());
} else {
doSomethingWithTheResult(*result);
}

## Operations

Depending on the level of access required to the API, there are one or more setup steps. Details are shown below with example code. Further details of the API are here documented in the Tilt Five™ Native Interface (C++).

## Create Client

292 // Create the client
293 auto client = tiltfive::obtainClient("com.tiltfive.test", "0.1.0", nullptr);
294 if (!client) {
295 std::cerr << "Failed to create client : " << client << std::endl;
296 std::exit(EXIT_FAILURE);
297 }
298 std::cout << "Obtained client : " << client << std::endl;
tiltfive::obtainClient
auto obtainClient(const std::string &applicationId, const std::string &applicationVersion, void *platformContext, const uint8_t sdkType=0) -> Result< std::shared_ptr< Client > >
Obtain an instance of the Tilt Five™ API client.
Definition: TiltFiveNative.hpp:1351

## System-wide Queries

System-wide queries require nothing more than a client to perform. Most of these operations are querying the service for common parameters such as the service version (tiltfive::Client::getServiceVersion()) and fixed physical parameters (tiltfive::Client::getGameboardSize()).

212 auto printGameboardDimensions(Client& client) -> tiltfive::Result<void> {
213 auto result = client->getGameboardSize(kT5_GameboardType_LE);
214 if (!result) {
215 return result.error();
216 }
217
218 float width = result->viewableExtentPositiveX + result->viewableExtentNegativeX;
219 float length = result->viewableExtentPositiveY + result->viewableExtentNegativeY;
220 float height = result->viewableExtentPositiveZ;
221
222 std::cout << "LE Gameboard size : " << width << "m x " << length << "m x " << height << "m"
223 << std::endl;
224
225 return tiltfive::kSuccess;
226}
227
229 auto printServiceVersion(Client& client) -> tiltfive::Result<void> {
230 auto result = client->getServiceVersion();
231 if (!result) {
232 return result.error();
233 }
234
235 std::cout << "Service version : " << result << std::endl;
236 return tiltfive::kSuccess;
237}
kT5_GameboardType_LE
@ kT5_GameboardType_LE
An LE gameboard.
Definition: types.h:103

## Enumerate & Select Glasses

44 auto waitForGlasses(Client& client) -> tiltfive::Result<Glasses> {
45 std::cout << "Looking for glasses..." << std::flush;
46
47 // Loop until we find glasses
48 auto glassesList = client->listGlasses();
49 if (!glassesList) {
50 return glassesList.error();
51 }
52 while (glassesList->empty()) {
53 std::cout << "." << std::flush;
54 std::this_thread::sleep_for(100_ms);
55
56 // Request a list of the available glasses
57 glassesList = client->listGlasses();
58 if (!glassesList) {
59 return glassesList.error();
60 }
61 }
62
63 // Print out the found glasses
64 for (auto& glassesInstance : *glassesList) {
65 std::cout << "Found : " << glassesInstance << std::endl;
66 }
67
68 // Return the first found glasses
69 return tiltfive::obtainGlasses(glassesList->front(), client);
70}
tiltfive::obtainGlasses
auto obtainGlasses(const std::string &identifier, const std::shared_ptr< Client > &client) -> Result< std::shared_ptr< Glasses > >
Obtain an instance of the Tilt Five™ Glasses.
Definition: TiltFiveNative.hpp:1365
std::flush
basic_ostream< _CharT, _Traits > & flush(basic_ostream< _CharT, _Traits > &__os)

## Non-exclusive Operations

Some glasses operations are available without acquiring exclusive access. These include operations to query stored parameters about glasses such as the IPD (tiltfive::Glasses::getIpd()) and user specified friendly name (tiltfive::Glasses::getFriendlyName()). Additionally, accessing the Wand Stream 'wand stream' does not require an exclusive connection.

133 // Get the friendly name for the glasses
134 // This is the name that's user set in the Tilt Five™ control panel.
135 auto friendlyName = glasses->getFriendlyName();
136 if (friendlyName) {
137 std::cout << "Obtained friendly name : " << friendlyName << std::endl;
138 } else if (friendlyName.error() == tiltfive::Error::kSettingUnknown) {
139 std::cerr << "Couldn't get friendly name : Service reports it's not set" << std::endl;
140 } else {
141 std::cerr << "Error obtaining friendly name : " << friendlyName << std::endl;
142 }
143
144 // Get the IPD for the glasses
145 // This is user set IPD in the Tilt Five™ control panel.
146 auto ipd = glasses->getIpd();
147 if (ipd) {
148 std::cout << "Obtained IPD : " << ipd << "m" << std::endl;
149 } else if (ipd.error() == tiltfive::Error::kSettingUnknown) {
150 std::cerr << "Couldn't get IPD : Service reports it's not set" << std::endl;
151 } else {
152 std::cerr << "Error obtaining IPD : " << ipd << std::endl;
153 return ipd.error();
154 }
tiltfive::Error::kSettingUnknown
@ kSettingUnknown
The requested param is unknown.

## Reserve for Exclusive

Other operations require exclusive access before a client can perform them. Only one client can have exclusive access to glasses at a time. Principally, obtaining the current pose (tiltfive::Glasses::getLatestGlassesPose()) and sending frames for rendering (tiltfive::Glasses::sendFrame()) require exclusive access.

Essentially, this is a process of repeatedly calling tiltfive::Glasses::reserve() until the function returns success. This can succeed even if glasses are not fully available (for example due to rebooting). Once the glasses are reserved, then it is time to ensure the glasses are ready for exclusive operations.

## Ensure Ready for Exclusive

After a pair of glasses has been reserved, they need to be made fully ready for exclusive operations (e.g., getting the current glasses pose and sending frames) using tiltfive::Glasses::ensureReady() . This may return an error that retry is needed if the glasses are not yet ready. Upon success, the glasses are ready for exclusive operations.

To automate the "reserve and ensure ready" process, use the GlassesConnectionHelper.

172 // Wait for exclusive glasses connection
173 auto connectionHelper = glasses->createConnectionHelper("Awesome game - Player 1");
174 auto connectionResult = connectionHelper->awaitConnection(10000_ms);
175 if (connectionResult) {
176 std::cout << "Glasses connected for exclusive use" << std::endl;
177 } else {
178 std::cerr << "Error connecting glasses for exclusive use : " << connectionResult
179 << std::endl;
180 return connectionResult.error();
181 }

## Exclusive Operations

Once an exclusive connection has been established, you may perform exclusive operations such as tiltfive::Glasses::getLatestGlassesPose() and tiltfive::Glasses::sendFrame().

110 auto readPoses(Glasses& glasses) -> tiltfive::Result<void> {
111 auto start = std::chrono::steady_clock::now();
112 do {
113 auto pose = glasses->getLatestGlassesPose(kT5_GlassesPoseUsage_GlassesPresentation);
114 if (!pose) {
115 if (pose.error() == tiltfive::Error::kTryAgain) {
116 std::cout << "\rPose unavailable - Is gameboard visible? ";
117 } else {
118 return pose.error();
119 }
120 } else {
121 std::cout << "\r" << pose;
122 }
123 } while ((std::chrono::steady_clock::now() - start) < 10000_ms);
124
125 return tiltfive::kSuccess;
126}
tiltfive::Error::kTryAgain
@ kTryAgain
Target is temporarily unavailable.
kT5_GlassesPoseUsage_GlassesPresentation
@ kT5_GlassesPoseUsage_GlassesPresentation
The pose will be used to render images to be presented on the glasses.
Definition: types.h:176
start
size_t start() const

## Release Exclusive Access

At any point after glasses have been reserved for exclusive use by a client, they can be released so that another client can reserve them exclusively instead without needing to destroy the whole glasses object or context. Use tiltfive::Glasses::release() to restore availability to other clients.

## Destruction

tiltfive::Client objects that fall out of scope automatically clean up their own resources. Likewise, tiltfive::Glasses that fall out of scope have their resources release. No explicit release is required in either case.

Note that objects in the C++ binder maintain internal references as necessary. Therefore, even if you no longer have a reference to a tiltfive::Client for example, but do still have a reference tiltfive::Glasses the client will not be released until the glasses object goes out of scope. For further details see Object References below.

# Wand Stream

Information about wand events is not delivered separately for each connected wand, but is instead multiplexed into a glasses global stream. Stream events are one of the following (T5_WandStreamEventType):

Event | Meaning
--- | ---
Connect | Glasses have detected a wand has connected.
Disconnect | Glasses have detected a wand disconnection.
Desync | The wand stream has desynchronized, and the client may have missed one or more connection, disconnection or report packets.
Report | A T5_WandReport is available detailing button, analog and positional data for a wand.

Simplistically, accessing the wand stream is done by configuring the stream (tiltfive::Glasses::configureWandStream()) and then repeatedly polling the stream (tiltfive::Glasses::readWandStream()).

To simplify this process, you can use the WandStreamHelper to automate the process and de-multiplex the stream into abstract tiltfive::Wand objects.

# Camera Image Stream

Access to the camera image stream requires both providing empty image buffers (tiltfive::Glasses::submitEmptyCamImageBuffer) and polling for filled image buffers (tiltfive::Glasses::getFilledCamImageBuffer).

The typical pattern should be to provide one or more buffers wrapped in a T5_CamImage wrapper (recommended minimum 3 for consistent framerate) via submitEmptyCamImageBuffer. Once provided, they will be filled automatically as images come in from the camera, and getFilledCamImageBuffer should be continually polled. After getFilledCamImageBuffer returns Success, the provided image buffer is valid to be used for image processing. Once processing is finished, the buffer can be resubmitted to the stream using submitEmptyCamImageBuffer again.

If receiving only the most recent image is desired instead, getFilledCamImageBuffer can be continually polled until an error is returned, and the most recent successful return will be the most recent image place into an empty buffer.

Continual polling of the filled image buffers and resubmission of empty buffers is expected to prevent filled buffers from becoming stale as new images come in from the headset.

When finished with the stream, all previously provided buffers should be cancelled via cancelCamImageBuffer before the buffer memory is freed.

# Object References

Objects in the C++ binder maintain internal references std::shared_ptr. The relationships are shown below. The direction of the arrow denotes a held reference. For example, a tiltfive::Wand object holds a reference to a tiltfive::WandStreamHelper, which in turn holds a reference to a tiltfive::Glasses and finally to a tiltfive::Client. Dashed lines represent std::weak_ptr.

# Helper Classes

All of the functionality of the glasses can be accessed using only tiltfive::Client and tiltfive::Glasses, however many of the calls are blocking and/or require polling. To simplify access to the functionality, there are several additional helper classes to automate these operations.

Helper | Purpose
--- | ---
tiltfive::WandStreamHelper | Monitor the wand event stream, and abstract access to tiltfive::Wand objects
tiltfive::GlassesConnectionHelper | Automate the exclusive connection process which requires repeated calling of tiltfive::Glasses::reserve()
tiltfive::ParamChangeHelper | Automate polling for changes to T5_ParamGlasses / T5_ParamSys and invoke callbacks on a tiltfive::ParamChangeListener subclasses

## Using WandStreamHelper

Simplistically, accessing the wand stream is done by configuring the stream (tiltfive::Glasses::configureWandStream()) and then repeatedly polling the stream (tiltfive::Glasses::readWandStream()).

To simplify this process, you can use a tiltfive::WandStreamHelper to automate the process and de-multiplex the stream into abstract tiltfive::Wand objects.

- Obtain a client
- Find glasses
- Invoke tiltfive::Glasses::getWandStreamHelper()
- Invoke tiltfive::WandStreamHelper::listWands() to obtain a std::vector <tiltfive::Wand>
- Invoke tiltfive::Wand::getLatestReport() to get wand reports

The tiltfive::WandStreamHelper automatically configures the stream to match the lifecycle of tiltfive::Wand objects - when no references are held, the stream is automatically stopped and restarted as needed.

## Using GlassesConnectionHelper

Connecting the glasses is essentially a process of repeatedly calling tiltfive::Glasses::reserve() until an exclusive connection is established. If the glasses are not fully available (for example due to rebooting), you may receive a reservation for the glasses instead. To automate this process use a tiltfive::GlassesConnectionHelper.

- Obtain a client
- Find glasses
- Invoke tiltfive::Glasses::createConnectionHelper() with glasses specific parameters
- Invoke tiltfive::GlassesConnectionHelper::awaitConnection() to wait for connection

or

- Poll tiltfive::Glasses::getConnectionState() while the glasses are automatically connected.

(new in 1.2.0) After the GlassesConnectionHelper is destroyed, the Glasses object will remain in its current connection state. (This is a change from NDK 1.1.0, where the Glasses would be released on GlassesConnectionHelper destruction). The Glasses will still be automatically released when the Glasses object itself is destroyed.

## Using ParamChangeHelper

Some of the parameters may change during operation (for example, the user want to adjust the IPD while using the glasses). To automatically detect changes and trigger a callback, use a tiltfive::ParamChangeHelper.

- Obtain a client.
- Subclass tiltfive::ParamChangeListener to create your listener.
- Create a std::shared_ptr to your listener.
- Invoke tiltfive::Client::createParamChangedHelper() with your shared listener to obtain a std::shared_ptr to a tiltfive::ParamChangeHelper.

At this point you'll be able to receive callbacks for system-wide changes via tiltfive::ParamChangeListener::onSysParamChanged(). To also receive parameter changes for individual glasses:

- Register each new tiltfive::Glasses instance with tiltfive::ParamChangeHelper::registerGlasses().
- De-register tiltfive::Glasses via tiltfive::ParamChangeHelper::deregisterGlasses() when you no longer wish to be notified about parameter changes for that instance of tiltfive::Glasses. At this point you'll also receive callbacks for glasses specific changes via tiltfive::ParamChangeListener::onGlassesParamChanged().