Getting Started with Streams
This guide demonstrates serialization and deserialization of game data using the Astralix streams module.
Basic Serialization
Write data to a JSON file:
#include "streams/serialization-context.hpp"
#include "streams/adapters/file/file-stream-writer.hpp"
// Create serialization context
auto ctx = SerializationContext::create(SerializationFormat::Json);
// Write data using operator[]
ctx["config"]["windowWidth"] = 1920;
ctx["config"]["windowHeight"] = 1080;
ctx["config"]["fullscreen"] = false;
ctx["config"]["volume"] = 0.8f;
// Serialize to file
ElasticArena arena(1024);
auto* block = ctx->to_buffer(arena);
auto buffer = std::make_unique<StreamBuffer>(1024);
buffer->write(block->ptr, block->size);
FileStreamWriter writer("config.json", std::move(buffer));
writer.write();
writer.flush();Basic Deserialization
Read data from a JSON file:
#include "streams/serialization-context.hpp"
#include "streams/adapters/file/file-stream-reader.hpp"
// Read file into buffer
FileStreamReader reader("config.json");
reader.read();
auto buffer = reader.get_buffer();
// Deserialize
auto ctx = SerializationContext::create(SerializationFormat::Json, std::move(buffer));
// Read data using operator[] and as<T>()
int width = ctx["config"]["windowWidth"].as<int>();
int height = ctx["config"]["windowHeight"].as<int>();
bool fullscreen = ctx["config"]["fullscreen"].as<bool>();
float volume = ctx["config"]["volume"].as<float>();Serializing Custom Types
Make your types serializable:
struct Player {
std::string name;
int level;
float position_x, position_y, position_z;
std::vector<int> inventory;
void Serialize(Ref<SerializationContext> ctx) const {
(*ctx)["player"]["name"] = name;
(*ctx)["player"]["level"] = level;
(*ctx)["player"]["position_x"] = position_x;
(*ctx)["player"]["position_y"] = position_y;
(*ctx)["player"]["position_z"] = position_z;
// Serialize array items
for (size_t i = 0; i < inventory.size(); i++) {
(*ctx)["player"]["inventory"][static_cast<int>(i)] = inventory[i];
}
}
void Deserialize(Ref<SerializationContext> ctx) {
name = (*ctx)["player"]["name"].as<std::string>();
level = (*ctx)["player"]["level"].as<int>();
position_x = (*ctx)["player"]["position_x"].as<float>();
position_y = (*ctx)["player"]["position_y"].as<float>();
position_z = (*ctx)["player"]["position_z"].as<float>();
// Deserialize array
auto inv_ctx = (*ctx)["player"]["inventory"];
size_t count = inv_ctx.size();
inventory.clear();
for (size_t i = 0; i < count; i++) {
inventory.push_back(inv_ctx[static_cast<int>(i)].as<int>());
}
}
};Note: The streams module currently supports only basic types (int, float, std::string, bool) as SerializableValue. Vector types like glm::vec3 must be decomposed into individual float components.
Nested Objects
Serialize complex hierarchies:
void SaveGameState(const GameState& state) {
auto ctx = SerializationContext::create(SerializationFormat::Json);
// Player data
state.player.Serialize(ctx);
// World data
(*ctx)["gameState"]["world"]["name"] = state.worldName;
(*ctx)["gameState"]["world"]["seed"] = state.seed;
(*ctx)["gameState"]["world"]["time"] = state.worldTime;
// Entities
for (size_t i = 0; i < state.entities.size(); i++) {
auto entity_ctx = SerializationContext::create(SerializationFormat::Json);
state.entities[i].Serialize(entity_ctx);
(*ctx)["gameState"]["entities"][static_cast<int>(i)] = entity_ctx;
}
// Write to file
ElasticArena arena(4096);
auto* block = ctx->to_buffer(arena);
auto buffer = std::make_unique<StreamBuffer>(4096);
buffer->write(block->ptr, block->size);
FileStreamWriter writer("save.json", std::move(buffer));
writer.write();
writer.flush();
}Memory Streams
Serialize to memory for network transmission:
// Serialize to buffer
auto ctx = SerializationContext::create(SerializationFormat::Json);
(*ctx)["messageType"] = std::string("PlayerUpdate");
(*ctx)["playerId"] = 42;
(*ctx)["position_x"] = 10.0f;
(*ctx)["position_y"] = 0.0f;
(*ctx)["position_z"] = 5.0f;
// Convert to buffer
ElasticArena arena(512);
auto* block = ctx->to_buffer(arena);
// Send buffer over network
SendToServer(block->ptr, block->size);
// Deserialize from buffer
auto recv_buffer = std::make_unique<StreamBuffer>(512);
recv_buffer->write(static_cast<char*>(block->ptr), block->size);
auto read_ctx = SerializationContext::create(SerializationFormat::Json, std::move(recv_buffer));
std::string msgType = (*read_ctx)["messageType"].as<std::string>();
int playerId = (*read_ctx)["playerId"].as<int>();
float pos_x = (*read_ctx)["position_x"].as<float>();
float pos_y = (*read_ctx)["position_y"].as<float>();
float pos_z = (*read_ctx)["position_z"].as<float>();Note: MemoryStreamReader is currently abstract and has no concrete implementation. MemoryStreamWriter exists but is missing its write() override. For now, use the arena-based buffer approach shown above.
Versioning
Support multiple data versions:
void Serialize(Ref<SerializationContext> ctx) const {
(*ctx)["save"]["version"] = 2; // Current version
// Version 2 data
(*ctx)["save"]["newField"] = newValue;
// Original data
(*ctx)["save"]["oldField"] = oldValue;
}
void Deserialize(Ref<SerializationContext> ctx) {
int version = 1; // Default to v1
// Check if version field exists using kind()
if ((*ctx)["save"]["version"].kind() != SerializationTypeKind::Unknown) {
version = (*ctx)["save"]["version"].as<int>();
}
if (version >= 2) {
newValue = (*ctx)["save"]["newField"].as<int>();
} else {
newValue = defaultValue; // Upgrade from v1
}
oldValue = (*ctx)["save"]["oldField"].as<int>();
}Note: The streams module does not have a HasKey() method. Use kind() to check if a field exists - it will return SerializationTypeKind::Unknown for missing keys.
Optional Fields
Handle missing fields gracefully:
void Deserialize(Ref<SerializationContext> ctx) {
// Required field
requiredValue = (*ctx)["config"]["required"].as<int>();
// Optional field with default
auto optional_proxy = (*ctx)["config"]["optional"];
if (optional_proxy.kind() != SerializationTypeKind::Unknown) {
optionalValue = optional_proxy.as<int>();
} else {
optionalValue = defaultValue;
}
}