Strings and UTF-8

StringType and StringViewType

Every string in the API is UTF-8. This section describes the C++ string types. In C, the functions take and return plain, null-terminated UTF-8 const char* strings, and strings inside result blobs are length-prefixed; see the C API page. In JavaScript, every string is an ordinary JavaScript string: the binding converts it to UTF-8 on the way into the native library and back on the way out, so no SDK string type appears in JavaScript code.

In C++, RealtimeCore::Common::StringType is an alias for std::u8string, StringViewType for std::u8string_view and CharType for char8_t; plain std::string never appears in a signature.

The aliases are defined in RealtimeCore/Common/StringType.h in the RealtimeCore::Common namespace and are used consistently across the whole API surface. Every string parameter, return value and data-type field (room names, player names, property keys, error messages) is a StringType or a StringViewType.

Building on char8_t makes the encoding part of the type system: a StringType is UTF-8 by construction, on every platform and on the wire. This removes the classic ambiguity of std::string, whose encoding depends on platform and locale, and turns accidental mixing of encodings into a compile error instead of corrupted text.

Writing String Literals

In C++, use the PHOTON_STR("...") macro for string literals. It expands to a u8"..." literal, so it produces char8_t text that converts directly to StringType and StringViewType. Writing u8"..." yourself is equivalent but discouraged: code that uses the macro keeps compiling when the SDK's internal string types change.

In C, an ordinary string literal is already the right type as long as the source file is saved as UTF-8. In JavaScript, a string literal is passed as it is.

The following passes string literals wherever the API takes a string:

C++
C
JavaScript

C++

using namespace RealtimeCore::Matchmaking;

ClientConstructOptions options;
options.AppId = PHOTON_STR("your-app-id");

RealtimeClient client(options);
client.SetPlayerName(PHOTON_STR("Alice"));

C

/* Every string on the ABI is a plain, null-terminated UTF-8 const char*. */
RealtimeHandle client = realtime_create("your-app-id", "1.0", queue);

realtime_set_player_name(client, "Alice");

/* There is no StringType, no PHOTON_STR and no char8_t over the C API: an
   ordinary string literal is already the right type, as long as the source
   file is saved as UTF-8. The encoding guarantee moves from the type
   system to a convention, so validate text that comes from a file, a
   socket or a platform SDK before passing it in. */

JavaScript

import { createRealtimeClient } from '@photonengine/realtime-core';

const client = await createRealtimeClient({ appId: 'your-app-id', appVersion: '1.0' });
client.setPlayerName('Alice');

Converting To and From Other String Types

In C++, RealtimeCore::Common::ToStringType(...) builds a StringType from the common sources: any integral value, a bool (formatted as True or False) and a raw CharType* pointer. It is handy when composing property values or log messages from numeric game state.

C++20 makes char8_t a distinct character type, so a UTF-8 std::string and a StringType do not convert into each other implicitly even when their bytes are identical. Interfacing with code that stores UTF-8 in std::string therefore needs an explicit conversion at the boundary. The conversion is a plain byte copy and is lossless in both directions, because both sides hold the same UTF-8 data. Make sure that any std::string you convert is encoded as UTF-8, or as ASCII, which is a subset of UTF-8.

In C, a UTF-8 char* needs no conversion on the way in; on the way out, strings read from a result blob are length-prefixed and not null-terminated.

In JavaScript, there is nothing to convert: every string the API takes or returns is a JavaScript string, including text outside the ASCII range. Text inside a raw byte payload, such as an event sent as a Uint8Array, has no string type: encode and decode it as UTF-8 with TextEncoder and TextDecoder.

The following converts text at the API boundary in both directions:

C++
C
JavaScript

C++

// std::string (holding UTF-8) to StringType:
std::string utf8Name = "Alice";
RealtimeCore::Common::StringType photonName(utf8Name.begin(), utf8Name.end());

// StringType back to std::string, e.g. for display or serialization:
std::string display(reinterpret_cast<const char*>(photonName.data()), photonName.size());

C

/* The rt_* blob helpers, copied from "Blob Helpers" on the C API page. */
#include "photon_blob.h"

/* Nothing to convert on the way in: a UTF-8 char* is the ABI's string type. */
const char* utf8Name = "Alice";
realtime_set_player_name(client, utf8Name);

/* On the way out, strings from a blob are length-prefixed and NOT terminated,
   so copy them into a buffer to get a C string. */
int32_t     nameLen = 0;
const char* name    = rt_get_str(&reader, &nameLen);

char display[64];
if (nameLen > (int32_t)sizeof(display) - 1)
{
    nameLen = (int32_t)sizeof(display) - 1;
}
memcpy(display, name, (size_t)nameLen);
display[nameLen] = '\0';

/* Or print in place, without the copy: */
printf("%.*s\n", (int)nameLen, name);

/* Text that a realtime_* function writes into a caller buffer, such as
   realtime_room_name or realtime_user_id, arrives null-terminated and
   needs neither the copy nor the precision. Blob strings are the ones that
   do not, because their length prefix is the only terminator they have. */

JavaScript

// JavaScript strings go in and come out as they are; the binding converts them to and from UTF-8.
client.setPlayerName('Zoë 🎮');
console.log(client.localPlayer.name); // Zoë 🎮

// A raw byte payload has no string type: encode and decode its UTF-8 text explicitly.
client.onEvent(7, ({ data }) => console.log(new TextDecoder().decode(data)), { asBytes: true });
client.sendEvent(7, new TextEncoder().encode('gg, wp 👍'));

Last updated on

Back to top