Titles targeting multiple platforms typically create platform abstractions to separate platform-dependent implementation details from platform-independent ones. This topic describes an asynchronous message-passing approach that works well for titles developed for Xbox One. The approach described herein is fundamentally designed for cross-platform titles, but there are best practices and other guidelines here that would also benefit titles developed exclusively for Xbox One.
Ideally, developers should read this topic before beginning to implement online-services code support in their titles.
In this topic:
A common practice in the design and implementation of online features for a multi-platform title is first to come up with an effective platform-abstraction architecture.
There are many goals and benefits of platform abstraction:
A game or game subsystem that follows the principles described in this topic consists of three layers:
Platform layer
This is the lowest layer in the architecture. Code at this layer interacts directly with the target system and is therefore platform-dependent. Any interaction between the platform layer and higher layers happens using platform-independent data types.
Abstraction layer
This is the middle layer in the architecture. It never makes system calls and is unaware of platform-dependent types. It interfaces directly with the platform layer using platform-independent types.
Application (or game) layer
This is the top layer in the architecture. Like the abstraction layer, it never makes system calls, and it is also unaware of platform-dependent types. But unlike the abstraction layer, it doesn’t interact directly with the platform layer. Instead, it always goes through the abstraction layer.
As an example of a subsystem that follows this three-layer architecture, consider a subsystem that allows you to display the online-presence status of the player’s friends in the UI. At the lowest layer, the platform layer, you would either poll or subscribe to system events that allow you to get online-presence status. Whenever a status change is detected or whenever a status result is available, the abstraction layer is notified and caches the result. The application layer queries the abstraction layer for the online-presence state. It’s guaranteed always to get the most up-to-date status, since the abstraction layer maintains the current state at all times. The abstraction layer might have its own polling strategy to ensure that this is the case.
When the time comes to implement this feature on another platform, you need only implement the platform layer for that target platform and to surface the results to the abstraction layer in the same way as on other target platforms.
The following diagram illustrates the platform-abstraction architecture and the interactions between the layers:

All pieces other than the platform-specific platform layers can be shared across all target platforms.
Correctly designing how data flows from one layer to the next in a platform-independent way is a key aspect of a platform-abstraction architecture. This topic presents an asynchronous model for the data flow that uses messages or events that are passed through queues.
An event is a data structure that contains information passed between layers in the architecture. When information is passed, it’s important for the receiving end to know what type of data it’s receiving, as along with the content of the data. A struct data type encapsulates this nicely. Here’s a simplified example demonstrating this.
struct Event
{
EventType myEventType;
EventPtr myEventPtr;
};
Where EventType is an enumeration of the type of events that flow through the system, and EventPtr is a union of pointers to the actual event data:
enum EventType(
Presence,
Reputation);
union EventPtr
{
PresenceEvent* myPresenceEvent;
ReputationEvent* myReputationEvent;
};
Events are added to event queues. They are then de-queued—processed—at a later time, usually in the update loop of the object owning a particular event queue. This queuing of events at one time and then de-queueing at a later time is what makes an event-based platform-abstraction architecture inherently asynchronous.
An event queue is not necessarily a priority queue. So it’s possible that a high-impact event, like one signaling loss of network connectivity, might be further up the queue than a lower-impact event. Titles must therefore take care to properly handle the cancellation or termination of in-flight async calls that are created before a high-impact event in the queue is processed.
Here’s a simplified event queue:
class EventQueue
{
public:
void AddEvent(SharedEvent& aSharedEvent);
bool GetNextEvent(SharedEvent& aOutSharedEvent);
private:
std::vector<SharedEvent> myEvents;
Concurrency::critical_section myLock;
};
SharedEvent is a smart pointer type:
typedef std::shared_ptr<Event> SharedEvent;
A synchronization primitive is used for synchronizing queuing and de-queueing operations, which might happen on different threads.
Putting all this together, here’s a typical update loop of an object that processes events from a particular event queue:
SharedEvent sharedEvent;
if(!myInQueue.GetNextEvent(sharedEvent))
return;
switch(sharedEvent.get()->myEventType)
{
case EventType::Presence:
PrivHandlePresenceEvent(sharedEvent.get()->myEventPtr.myPresenceEvent);
break;
case EventType::Reputation:
PrivHandleRepEvent(sharedEvent.get()->myEventPtr.myReputationEvent);
break;
default:
break;
}
The following examples are simplified examples of typical platform-abstraction classes, starting with the platform layer.
class PlatformLayer
{
public:
void Update();
void Initialize();
void AddInQueue(EventQueue* aInQueue);
void AddOutQueue(EventQueue* aOutQueue);
protected:
EventQueues myInQueues;
EventQueues myOutQueues;
};
In order for the PlatformLayer class to handle any incoming events, one or more EventQueue instances have to be added using the AddInQueue function.
For example, say you have two objects at the abstraction layer, one for managing presence and another for managing matchmaking. Each object might make requests to PlatformLayer—for instance, to fetch a friend’s presence or begin matchmaking. Assume, too, that each object contains an EventQueue instance specifically for sending events to PlatformLayer. Then in this case, each EventQueue instance would be added separately by calling the AddInQueue function.
Analogously, for PlatformLayer to communicate outward with objects at higher layers, one or more EventQueue instances have to be added using the AddOutQueue.
Let’s look at a typical class definition at the abstraction layer. Again, it’s very simplified and illustrates the basic principles described in this document.
class AbstractionLayer
{
public:
void SetToGameQueue(EventQueue* aQueue);
void Initialize();
void Update();
EventQueue& GetIncomingQueue();
private:
EventQueue myIncomingQueue;
EventQueue myToPlatformQueue;
EventQueue* myToGameQueue;
};
In this example, notice that AbstractionLayer exposes its internal incoming EventQueue instance through the GetIncomingQueue function. It also has a function that allows a caller to set the game (application) layer’s queue.
Finally, here is the game, or application layer, simplified for demonstration purposes:
class ApplicationLayer
{
public:
void Initialize();
void Update();
private:
EventQueue myIncomingQueue;
AbstractionLayer1 myAbstractionLayer1;
AbstractionLayer2 myAbstractionLayer2;
};
In the Initialize function, ApplicationLayer initializes its myAbstractionLayer1 and myAbstractionLayer2 members and calls their SetToGameQueue function to pass in its myIncomingQueue member. In the Update function, ApplicationLayer polls myIncomingQueue for any pending events and processes them, as described in the Events and event queues section.
Only the platform layer has any knowledge of platform-dependent types and references platform-dependent code. The abstraction layer and application layer are aware only of platform-independent types. Isolating platform dependencies in this way-that is, to the lowest layer only-allows a significant portion of the code-base to be reused across all platforms.
Because only the platform layer is aware of platform-dependent types, all data types passed as context through event queues must be platform-independent. Therefore, a conversion from a platform-dependent type to platform-independent type—for example, from Platform::String^ to std::wstring on Xbox One—is necessary every time the data behind a platform-dependent type is passed as context out of the platform layer. In the opposite direction, events passed to the platform layer also contain platform-independent types. In many cases, these types will have to be converted to something platform-dependent before they can be passed to a platform-dependent API.
For example: Something seen universally in all multi-platform games is the ID representing a user. On Xbox One, it’s a WinRT string type; on Xbox 360, it’s a 64-bit number; on other platforms it might be a string or even some other data structure. On any of these target platforms, the platform layer in a platform-abstracted system would have to convert the platform-dependent ID type to something platform-independent, such as a byte buffer. Here’s what a platform-independent user ID class might look like to facilitate this:
class UserId
{
public:
UserId();
UserId& operator=(const UserId& aRef);
bool operator==(const UserId& aRef) const;
bool operator!=(const UserId& aRef) const;
void Set(const unsigned char aIdBuffer[], size_t aIdBufferSize);
void Set(const UserId& aUserId); const std::vector<unsigned char>& Get() const;
private:
std::vector<unsigned char> myData;
};
At the platform layer, conversion functions would exist to go from a platform-dependent user ID type to a UserId, and vice-versa.
In a platform-abstracted architecture, when a game consumes system events at the platform layer, it converts the event data into platform-independent data structures suitable for consumption by higher-level layers. However, it’s more convenient to work directly with the platform-dependent type in those parts of the code-base that are allowed to be platform-dependent.
A common strategy for achieving this is through a type factory. Typically, the platform layer gets a new instance of a platform-dependent type from the type factory, then wraps this instance in a ref-counted object, such as std::shared_ptr, before passing it on to higher-level layers. Although such instances might be platform-dependent, they all inherit from a base class that exposes only platform-independent data.
For example, suppose a type library exposes a function to create an object representing a user, like this:
virtual PlatformUserBase* CreatePlatformUser() = 0;
Here’s how it might be implemented on Xbox One:
PlatformUserBase* PlatformFactory::CreatePlatformUser()
{
#ifdef _DURANGO
return new XboxOne::PlatformUserXboxOne();
#else
XSF_ASSERT("Unsupported platform!")
return NULL;
#endif
}
A full code implementation of the principles described in this document will ship in a future Xbox One Development Kit (XDK). If you would like to obtain a preview version, contact your Developer Account Manager.
The implementation is split into four parts:
Designing a platform-abstraction strategy is something that every multi-platform title developer comes face-to-face with during development. The sooner one architects a solution, the better, because then the many benefits of a platform-abstraction solution will be available earlier in the development lifecycle. The platform-abstraction strategy described in this paper separates platform-dependent code from platform-independent code into three distinct layers. Because communication between the layers is accomplished through asynchronous message passing, the entire architecture is asynchronous from the ground up. On Xbox One in particular, this makes using the lambda expression syntax for handling system callbacks an attractive option. Regardless of what thread and in what order your callbacks are called, your code has complete control of when, in what order, and at what time the underlying system messages are processed.
On Xbox One, a player’s user ID is represented as a Platform::String type. As mentioned earlier in this document, a UserId type can be used to represent a user ID in a platform-independent way. Here’s a simplified version of the PlatformUserXboxOne class as implemented in the reference sample that shows how to efficiently store an XboxUserId and reference it in a platform-independent way.
class PlatformUserXboxOne : public PlatformUserBase
{
public:
PlatformUserXboxOne();
~PlatformUserXboxOne();
PlatformUserXboxOne& operator=(const PlatformUserXboxOne& aRef);
virtual bool AreEqual(const PlatformUserBase& aRef);
virtual const wchar_t* GetIdAsWideString() const;
Platform::String^ GetXboxUserId() const;
private:
mutable Platform::String^ myXboxUserId;
};
?
PlatformUserXboxOne::PlatformUserXboxOne()
: myXboxUserId(nullptr)
{
}
PlatformUserXboxOne&
PlatformUserXboxOne::operator=(
const PlatformUserXboxOne& aRef)
{
myXboxUserId = nullptr;
PlatformUserBase::operator=(aRef);
return *this;
}
bool
PlatformUserXboxOne::AreEqual(
const PlatformUserBase& aRef)
{
if(dynamic_cast<const PlatformUserXboxOne*>(&aRef) == NULL)
return false;
Platform::String^ myXuid = GetXboxUserId();
Platform::String^ theirXuid = (dynamic_cast<const PlatformUserXboxOne*>(&aRef))->GetXboxUserId();
return StringHelper::AreStringsEqualCaseInsenstive(myXuid, theirXuid);
}
const wchar_t*
PlatformUserXboxOne::GetIdAsWideString() const
{
Platform::String^ xboxUserId = GetXboxUserId();
return xboxUserId->Data();
}
Platform::String^
PlatformUserXboxOne::GetXboxUserId() const
{
if(myXboxUserId != nullptr)
return myXboxUserId;
if(myUserId.Get().size() == 0)
return nullptr;
XUID xuid = *(XUID*)&myUserId.Get()[0];
myXboxUserId = StringHelper::XboxUserIdFromXuid(xuid);
return myXboxUserId;
}
XUID is defined in the following way:
typedef unsigned long long XUID;
Here are implementations of the StringHelper::XboxUserIdFromXuid helper function referenced above:
Platform::String^ StringHelper::XboxUserIdFromXuid(XUID aXuid)
{
wchar_t buffer[64];
const int cchWritten = swprintf_s(buffer, L"%llu", aXuid);
if(cchWritten == -1)
return nullptr;
buffer[cchWritten] = L'\0';
return ref new Platform::String(buffer);
}
And the following helper function goes in the opposite direction:
XUID StringHelper::XuidFromXboxUserId(Platform::String^ aXboxUserId)
{
if(aXboxUserId == nullptr)
return 0;
const XUID xuid = _wcstoui64(aXboxUserId->Data(), NULL, 10);
if(xuid == 0 || xuid == _UI64_MAX)
{
XSF_ASSERT("string was not a valid XUID representation");
}
return xuid;
}