Skip to content

Data structures and Utilities

This section covers the core utility libraries that provide the fundamental data structures, abstractions, and common definitions shared across the Phoenix2 ecosystem.

These libraries extend and simplify the use of standard C++ facilities by providing Phoenix-specific wrappers and utilities around common types such as std::string, std::filesystem::path, streams, and other frequently used components. They serve as the foundation upon which many other Phoenix2 modules are built.

Logger

Circular Buffer and Index

Phoenix2 provides an implementation for circular buffer classes in PhoenixCircularBuffer. A circular buffer (also known as a ring buffer) is a fixed-size data structure designed to efficiently store a continuous stream of data.

Unlike a standard container that continuously grows as new elements are inserted, a circular buffer reuses a pre-allocated block of memory. Once the buffer reaches its capacity, new elements overwrite the oldest ones (or are rejected, depending on the implementation).

Why use a circular buffer?

Circular buffers are particularly useful when:

  • only the most recent N elements need to be retained;
  • memory usage must remain constant and predictable;
  • high-performance applications require frequent insertions without dynamic memory allocations.

Typical use cases include:

  • data acquisition systems;
  • real-time monitoring;
  • logging and event history;
  • communication queues;
  • signal and sensor processing.

How it works

A circular buffer maintains two logical positions:

  • a write position, where the next element will be inserted;
  • a read position (or the oldest element), indicating where data should be read or removed.

When the write position reaches the end of the allocated storage, it wraps around to the beginning, forming a logical ring.

Capacity = 8

+---+---+---+---+---+---+---+---+
| A | B | C | D | E |   |   |   |
+---+---+---+---+---+---+---+---+
  ^
 oldest                     ^
                         next write

After additional insertions:

+---+---+---+---+---+---+---+---+
| I | J | C | D | E | F | G | H |
+---+---+---+---+---+---+---+---+
      ^
   oldest

The newest elements are always preserved while the oldest ones are discarded as the buffer wraps around.

Description

Advantages

  • Constant memory usage.
  • Fast insertion and access operations.
  • No memory reallocations after construction.
  • Well suited for real-time and high-throughput applications.

Tree structure

What is a Tree

A tree is a hierarchical data structure composed of nodes connected through parent-child relationships. Unlike linear containers such as arrays or linked lists, a tree organizes data into multiple levels, allowing information to be structured as a hierarchy.

Every tree starts with a single root node. From this root, each node can have zero, one, or many child nodes. Nodes without children are called leaf nodes, while intermediate nodes are used to organize and group related data.

Trees are widely used whenever data naturally follows a hierarchical organization.

Typical examples include:

  • file system hierarchies;
  • configuration trees;
  • routing tables;
  • scene graphs;
  • organizational structures;
  • decision trees.

Traversing a tree

Data is accessed by following a path from the root to a target node. Each step of the path identifies one child of the current node.

For example:

(root)
├── Communication
│   ├── Sender 1
│   │   ├── Receiver A
│   │   │   └── Statistics
│   │   └── Receiver B
│   └── Sender 2
└── Computing
    └── GPU
        └── Statistics

The following path uniquely identifies a node:

Communication → Sender 1 → Receiver A → Statistics

Each element of the path is called a key and is used to select the next child node.

PhoenixTree

PhoenixTree is a generic implementation of a hierarchical tree designed to support heterogeneous keys.

Unlike traditional tree implementations, where all keys are required to have the same type (for example, all std::string or all int), PhoenixTree allows each level of the hierarchy to use any key type.

For example, a path may combine completely different key types:

Communication        (std::string)
8536                 (int)
2225.0               (double)
DataType::Raw        (enum)
Statistics

or another branch may use an entirely different hierarchy:

Computing            (std::string)
3.14159              (double)
DataType::Image      (enum)
Statistics

There is no restriction on the types used for the keys. Primitive types, enumerations, strings, or user-defined classes can all be used together within the same tree.

Each node stores its children in a hash table, providing efficient lookups while preserving complete flexibility regarding key types. Nodes may also optionally store a value, allowing the tree to represent both the hierarchy itself and application-specific data associated with any node.