Basic Operations
Key-Value Store
Riak is a key-value store, which means that all data is stored as a key and a value. To be able to organize data better, Riak requires all key-value-pairs to belong to one bucket. A bucket is similar to a dictionary or hashtable, where binary value-chunks are stored under string keys. The fundamental operations in uGameDB for interacting with Riak is Set(), Get() and Remove(). All three of these methods lie in the the Bucket class, which is a client-side representation of a Riak bucket.Asynchronous Operations
When interacting with an external database it is not certain that requests will return immediately. The request needs to be sent over the network and received by the database server. Then the value needs to be stored or fetched from disk and returned over the network again. Even though Riak's distributed design makes it much faster than a transaction-oriented database, there are still many factors that can affect the time one request will take, such as network condition and the load on the responding node. Because of this, uGameDB handles requests asynchronously, so that when you send a request from your code, it will be executed on a separate background thread. In order to keep the user from having to manage these threads, uGameDB provides two patterns to interact with these asynchronous operations.- Unity Coroutines - By sending your requests from coroutines you can write your database interaction sequences as if they were synchronous. This pattern produces the simplest and most maintainable code, and you should use it as much as you can.
- Callbacks - You can also assign callbacks for success and failure when you send your requests. This pattern is probably most familiar to non-Unity developers and have been included for this reason.
The Coroutine Pattern
The CoroutineExample component only contains a Start() method which is a coroutine, as denoted by the IEnumerator return type. Because it is one of the default Unity callbacks, there is no need to call StartCoroutine(). When the component starts, it sends a set request to the database. Then it uses a coroutine yield to wait for the request to complete. Request.WaitUntilDone() returns a Coroutine object of its own that the Start() coroutine can wait for. This causes the Start() coroutine to wait until the Request.isDone property is true, which means that a response (either success or error) has returned from the database. Start() then prints an error message if the request failed, or continues to make a get request to the database to read the previously written value. It then yields in the same manner as for the set request and waits until the get request has finished, and finally prints the result.using System.Collections;
using UnityEngine;
using uGameDB;
public class CoroutineExample : MonoBehaviour
{
private Bucket scoresBucket = new Bucket("high-scores");
private IEnumerator Start()
{
// Write a high-score entry to the database.
var setRequest = scoresBucket.Set("easy-steve", 6100, Encoding.Json);
// Wait for the write operation to complete.
yield return setRequest.WaitUntilDone();
if (setRequest.hasFailed)
{
Debug.LogError("Write high-score failed! " + setRequest + ", "
+ setRequest.GetError() + ", " + setRequest.GetErrorString());
yield break;
}
// Read a high-score entry from the database.
var getRequest = scoresBucket.Get("easy-steve");
// Wait for the read operation to complete.
yield return getRequest.WaitUntilDone();
if (getRequest.hasFailed)
{
Debug.LogError("Read high-score failed! " + getRequest + ", "
+ getRequest.GetError() + ", " + getRequest.GetErrorString());
yield break;
}
Debug.Log("Read high-score " + getRequest.GetValue<int>() + " for player '"
+ getRequest.key + "'.");
}
}
The Callback Pattern
The CallbackExample component performs the exact same sequence as CoroutineExample and produces the exact same output. However, it only uses callback methods to piece together this sequence. This means that the sequence is split over several methods and it can be tricky for someone else to see the sequence flow without some documentation. When Start() is called, the set request is sent, and two callbacks are assigned to the request, one for a successful result and one for an error result. The error callback simply prints the error log message, while the success callback issues the get request. The get request has similarly defined callbacks, a success callback that prints the result, and an error callback that prints the error message.using UnityEngine;
using uGameDB;
public class CallbackExample : MonoBehaviour
{
private Bucket scoresBucket = new Bucket("high-scores");
private void Start()
{
// Write a high-score entry to the database.
// When the write operation completes, OnSetSuccess or OnSetError will be called.
scoresBucket.Set("easy-steve", 6100, Encoding.Json, OnSetSuccess, OnSetError);
}
private void OnSetSuccess(SetRequest<int> request)
{
// Read a high-score entry from the database.
// When the read operation completes, OnGetSuccess or OnGetError will be called.
scoresBucket.Get("easy-steve", OnGetSuccess, OnGetError);
}
private void OnSetError(SetRequest<int> request)
{
Debug.LogError("Write high-score failed! " + request + ", " + request.GetError()
+ ", " + request.GetErrorString());
}
private void OnGetSuccess(GetRequest request)
{
Debug.Log("Read high-score " + request.GetValue<int>() + " for player '"
+ request.key + "'.");
}
private void OnGetError(Request request)
{
Debug.LogError("Read high-score failed! " + request + ", " + request.GetError()
+ ", " + request.GetErrorString());
}
}
These examples illustrate the fact that the same methods are used to send the request regardless of which pattern you use. The callback arguments to Bucket.Set(), Bucket.Get(), etc, are optional. If you don't specify callback, they will default to null, which means that the callback will not be invoked. It is also possible to omit one of them. To make the set request with only a success callback you can call
scoresBucket.Set("easy-steve", 6100, Encoding.Json, OnSetSuccess, null);
or if you want to make the same request with only an error callback you can call the following.
scoresBucket.Set("easy-steve", 6100, Encoding.Json, null, OnSetError);
It is possible to mix the two patterns and if you need to do this, then it is worth noting that Request.isDone does not become true until any callback has finished. This means that if you use both yield to wait for completion and a callback then the callback will be completed before the coroutine resumes. It also means that if your callback does something that takes a long time to finish, the coroutine will not resume until this is done. Consider this when you design your operations.
Together, the coroutine and callback patterns place a lot of power and flexibility at your fingertips, and give you the tools you need to write even the most intricate database interaction sequences.






