Ember
Loading...
Searching...
No Matches
EmberMonitor Design Document

1. Executive Summary

This document outlines the design for EmberMonitor, a real-time behavior tree monitoring application that complements EmberForge (the XML editor). While EmberForge edits behavior tree XML files, EmberMonitor receives tree structure and execution state over TCP from external systems (games, robots, simulations) and visualizes them in real-time.

1.1 Goals

Goal Description
Live Monitoring Visualize behavior tree execution in real-time
Zero XML Dependency Tree structure received entirely over TCP
Code Reuse Maximize sharing with EmberForge via EmberCore
Low Latency Minimal delay between tick and visualization update
Multi-Tree Support Monitor multiple trees from multiple sources
Easy Integration FlatBuffers schema + reference client for any language

1.2 Non-Goals

  • EmberMonitor will NOT edit trees (read-only visualization)
  • EmberMonitor will NOT save/load XML files
  • EmberMonitor will NOT execute behavior trees (only visualize)

2. System Architecture

2.1 High-Level Overview

┌──────────────────────────────────────────────────────────────────────────────┐
│ EmberCore (Shared Library) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Core │ │ Parsers │ │ Adapters │ │ Interfaces │ │
│ │ Node, Tree, │ │ XML (Forge) │ │ DirectTree │ │ ITreeNode │ │
│ │ Blackboard │ │ │ │ NodeAdapter │ │ ITreeStruct │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └───────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Network Module │ │
│ │ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌────────────┐ │ │
│ │ │ FlatBuffers │ │ FlatBuffer │ │ TreeBuilder │ │TickStateMgr│ │ │
│ │ │ Schema │ │ Codec │ │ (Construct) │ │ (State) │ │ │
│ │ └─────────────┘ └──────────────┘ └─────────────┘ └────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ EmberUI (Shared UI Library) │
│ ┌────────────────┐ ┌──────────────────────┐ ┌─────────────────────────┐ │
│ │ Components │ │ Tabs │ │ Visualization │ │
│ │ Panel │ │ NavigatorTab │ │ TreeCanvas │ │
│ │ SidePanel │ │ PropertiesTabBase │ │ StatusOverlay │ │
│ └────────────────┘ │ TreeHierarchyTab │ └─────────────────────────┘ │
│ ┌────────────────┐ └──────────────────────┘ │
│ │ Interfaces │ │
│ │ ITab, IPanel │ │
│ │ IScene │ │
│ └────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ EmberForge │ │ EmberMonitor │
│ (Edit Mode) │ │ (Monitor Mode) │
├─────────────────────────────────────┤ ├─────────────────────────────────────┤
│ • XML Load/Save │ │ • TCP Server (accept connections) │
│ • Full Editing UI │ │ • Real-time State Visualization │
│ • DirectTreeAdapter │ │ • TCPTreeAdapter │
│ • Node Creation/Deletion │ │ • Read-Only Visualization │
│ • Property Editing │ │ • MonitorTreeCanvas │
│ • Blackboard Editing │ │ • Navigator + Properties Tabs │
└─────────────────────────────────────┘ └─────────────────────────────────────┘

2.2 Component Responsibilities

Component Location Responsibility
EmberCore Shared Core data structures, interfaces, adapters
Network Module EmberCore (new) FlatBuffers schema, codec, tree building
EmberForge Standalone App XML editing, full tree manipulation
EmberMonitor Standalone App TCP reception, read-only visualization
Client SDK Distributed Schema file + C++ reference client for user integration

2.3 Shared vs. Separate Code

Shared (EmberCore):
├── Core/
│ ├── Node.h/.cpp # Reused as-is
│ ├── BehaviorTree.h/.cpp # Reused as-is
│ ├── Blackboard.h/.cpp # Reused as-is
│ └── BehaviorTreeValidator.h/.cpp # Reused as-is
├── Interfaces/
│ ├── ITreeNode.h # Reused as-is
│ ├── ITreeStructure.h # Reused as-is
│ └── ITreeFactory.h # Reused as-is
├── Adapters/
│ ├── NodeAdapter.h/.cpp # Reused as-is
│ └── DirectTreeAdapter.h/.cpp # Reused as-is
├── Types/ # Reused as-is
├── Utils/ # Reused as-is
└── Network/ (NEW)
├── Schema/
│ ├── ember_protocol.fbs # FlatBuffers schema (source of truth)
│ └── generated/ # Auto-generated from schema
├── Codec/
└── Builder/
EmberUI (Shared UI Library):
├── Interfaces/
│ ├── ITab.h # Tab interface
│ ├── IPanel.h # Panel interface
│ └── IScene.h # Scene interface
├── Components/
│ ├── Panel.h/.cpp # Base panel class
│ ├── SidePanel.h/.cpp # Tabbed side panel
│ └── ScalableDialog.h/.cpp # DPI-aware dialog
├── Tabs/
│ ├── NavigatorTab.h/.cpp # Tree list + hierarchy + search
│ ├── PropertiesTabBase.h/.cpp # Shared property editor base
│ └── TreeHierarchyTab.h/.cpp # Tree hierarchy view
├── Visualization/
│ ├── TreeCanvas.h/.cpp # Shared tree rendering canvas
│ └── StatusOverlay.h/.cpp # Status colors and IStatusProvider
└── Utils/
├── DPI.h # DPI scaling utilities
├── LayoutConstants.h # Layout constants
└── ProportionalLayout.h/.cpp # Proportional AUI layout
EmberForge-Only:
├── Parsers/
│ ├── LibXMLBehaviorTreeParser # XML-specific
│ └── LibXMLBehaviorTreeSerializer # XML-specific
└── [All current UI code]
EmberMonitor-Only (NEW):
├── App/
│ ├── MonitorApp.h/.cpp
│ ├── MonitorFrame.h/.cpp
│ └── MonitorConstants.h
├── Network/
│ └── TCPServer.h/.cpp # TCP server (accept connections)
├── Panels/
│ ├── ConnectionPanel.h/.cpp
│ ├── MainPanel.h/.cpp
│ └── RightSidePanel.h/.cpp
├── Tabs/
│ ├── MonitorNavigatorTab.h/.cpp # Navigator (extends NavigatorTab)
│ └── PropertiesTab.h/.cpp # Properties (extends PropertiesTabBase)
└── Visualization/
└── MonitorTreeCanvas.h/.cpp # Monitor tree canvas (extends TreeCanvas)
Client SDK (Distributed):
├── README.md # Integration guide
├── ember_protocol.fbs # Schema file (source of truth)
└── cpp/
├── EmberClient.h # Header-only reference client
├── flatbuffers/ # FlatBuffers runtime headers
└── example/
├── CMakeLists.txt
└── main.cpp # Minimal usage example

3. Protocol Specification (FlatBuffers)

3.1 Why FlatBuffers

Requirement FlatBuffers Advantage
Performance Zero-copy deserialization, minimal CPU overhead
Low Latency No parsing step - direct memory access
Multi-Language Auto-generates C++, C#, Python, Rust, Go, Java, etc.
No Runtime Deps Header-only for C++, no .dll/.so required
Easy Integration Users just include generated files

3.2 Protocol Overview

The protocol uses FlatBuffers binary serialization over TCP with a simple framing layer.

┌──────────────────────────────────────────────────────────────────┐
│ Message Frame │
├──────────────┬──────────────┬────────────────────────────────────┤
│ Length (4B) │ Type (1B) │ FlatBuffer Payload │
│ Little-End │ MessageType │ (Binary) │
└──────────────┴──────────────┴────────────────────────────────────┘

Frame Header (5 bytes):

  • Bytes 0-3: Payload length (little-endian uint32)
  • Byte 4: Message type enum

3.3 FlatBuffers Schema

The complete protocol is defined in a single schema file:

ember_protocol.fbs

// EmberMonitor Protocol Schema
// Version: 1.0.0
namespace Ember.Protocol;
// ============================================================================
// Enumerations
// ============================================================================
enum MessageType : ubyte {
Handshake = 1,
HandshakeAck = 2,
TreeInit = 16, // 0x10
TreeInitAck = 17, // 0x11
TickUpdate = 32, // 0x20
TickUpdateBatch = 33, // 0x21
BlackboardUpdate = 48,// 0x30
TreeReset = 64, // 0x40
Disconnect = 240, // 0xF0
Error = 255 // 0xFF
}
enum NodeStatus : ubyte {
Idle = 0,
Running = 1,
Success = 2,
Failure = 3,
Halted = 4
}
enum NodeType : ubyte {
Action = 0,
Control = 1,
Condition = 2,
Decorator = 3,
SubTree = 4
}
enum ErrorCode : ushort {
None = 0,
ProtocolMismatch = 1,
InvalidMessage = 2,
UnknownTree = 3,
UnknownNode = 4,
InternalError = 500
}
// ============================================================================
// Common Structures
// ============================================================================
table KeyValue {
key: string (required);
value: string;
}
// ============================================================================
// Handshake Messages
// ============================================================================
table Handshake {
version: string (required); // Protocol version "1.0"
client_id: string (required); // Unique client identifier
client_name: string; // Human-readable name
capabilities: [string]; // Supported features
}
table HandshakeAck {
version: string (required); // Monitor's protocol version
session_id: string (required); // Assigned session ID
accepted: bool = true;
error: string; // Error message if not accepted
}
// ============================================================================
// Tree Structure Messages
// ============================================================================
table NodeDefinition {
id: int64; // Unique node ID within tree
node_type: NodeType; // Action, Control, etc.
subtype: string (required); // "Sequence", "Attack", etc.
name: string (required); // Node instance name
description: string; // Human-readable description
attributes: [KeyValue]; // Custom attributes
children: [NodeDefinition]; // Child nodes (recursive)
}
table BlackboardEntry {
key: string (required);
value_type: string; // Type for display ("int", "float", etc.)
value: string; // Serialized value
description: string;
}
table BlackboardDefinition {
id: string (required); // Blackboard identifier
name: string; // Human-readable name
entries: [BlackboardEntry];
}
table TreeInit {
tree_id: string (required); // Unique tree identifier
tree_name: string; // Human-readable name
root: NodeDefinition (required); // Root node (contains full tree)
blackboards: [BlackboardDefinition];
metadata: [KeyValue]; // Additional metadata
}
table TreeInitAck {
tree_id: string (required);
success: bool = true;
node_count: int32; // Number of nodes constructed
error: string;
}
// ============================================================================
// Tick Update Messages
// ============================================================================
table NodeState {
id: int64; // Node ID
status: NodeStatus; // Current status
last_result: NodeStatus; // Previous tick result
tick_count: int64; // Times this node was ticked
message: string; // Debug message from node
}
table TickUpdate {
tree_id: string (required);
tick_number: int64;
tick_timestamp_ms: int64; // Unix timestamp in milliseconds
delta_time_ms: float; // Time since last tick
is_delta: bool = false; // True = only changed states included
states: [NodeState] (required);
execution_path: [int64]; // Ordered list of executed node IDs
}
table TickUpdateBatch {
tree_id: string (required);
ticks: [TickUpdate] (required); // Multiple ticks for replay/recording
}
// ============================================================================
// Blackboard Update Messages
// ============================================================================
table BlackboardUpdateEntry {
key: string (required);
value: string;
previous_value: string;
}
table BlackboardUpdate {
tree_id: string (required);
blackboard_id: string (required);
tick_number: int64;
updates: [BlackboardUpdateEntry];
}
// ============================================================================
// Control Messages
// ============================================================================
table TreeReset {
tree_id: string (required);
tick_number: int64; // Reset tick counter to this value
reason: string; // "level_restart", "manual", etc.
}
table Disconnect {
reason: string;
message: string;
}
table Error {
code: ErrorCode;
message: string;
fatal: bool = false; // If true, connection will be closed
}
// ============================================================================
// Root Types (for verification)
// ============================================================================
root_type TickUpdate; // Most common message, used for default

3.4 Message Types Summary

Type ID Message Direction Purpose
0x01 Handshake Client → Monitor Initial connection
0x02 HandshakeAck Monitor → Client Acknowledge connection
0x10 TreeInit Client → Monitor Send full tree structure
0x11 TreeInitAck Monitor → Client Acknowledge tree received
0x20 TickUpdate Client → Monitor Node state changes
0x21 TickUpdateBatch Client → Monitor Multiple ticks (replay)
0x30 BlackboardUpdate Client → Monitor Blackboard changes
0x40 TreeReset Client → Monitor Tree reset notification
0xF0 Disconnect Either Graceful disconnect
0xFF Error Either Error notification

3.5 Protocol State Machine

┌─────────────┐
│ DISCONNECTED│
└──────┬──────┘
│ TCP Connect
┌─────────────┐ Handshake ┌─────────────────┐
│ CONNECTED │──────────────▶│ AWAITING_ACK │
└─────────────┘ └────────┬────────┘
│ HandshakeAck
┌─────────────────┐
│ AUTHENTICATED │
└────────┬────────┘
│ TreeInit
┌─────────────────┐
│ AWAITING_TREE │
└────────┬────────┘
│ TreeInitAck
┌─────────────────┐
│ ACTIVE │◀──┐
└────────┬────────┘ │
│ │
┌──────────────────┼────────────┤
│ │ │
▼ ▼ │
TickUpdate BlackboardUpdate │
│ │ │
└──────────────────┴────────────┘
│ Disconnect / Error (fatal)
┌─────────────────┐
│ DISCONNECTED │
└─────────────────┘

3.6 Binary Wire Format Examples

3.6.1 TickUpdate Message (Most Common)

Wire format for a TickUpdate with 3 node states:
Header (5 bytes):
┌────────────────────────────────────────┬─────────┐
│ 00 00 00 4C │ 20 │
│ Length = 76 bytes (little-endian) │ Type=32 │
└────────────────────────────────────────┴─────────┘
Payload (76 bytes):
┌─────────────────────────────────────────────────────────────┐
│ FlatBuffer binary data │
│ - tree_id offset → "player_ai" │
│ - tick_number = 1542 │
│ - states vector → [ │
│ {id:1, status:Running, tick_count:1542}, │
│ {id:2, status:Success, tick_count:45}, │
│ {id:3, status:Idle, tick_count:45} │
│ ] │
│ - execution_path → [1, 2] │
└─────────────────────────────────────────────────────────────┘
Total: 81 bytes (vs ~400 bytes for equivalent JSON)

3.6.2 Size Comparison

Message Type JSON Size FlatBuffer Size Reduction
TickUpdate (10 nodes) ~800 bytes ~150 bytes 81%
TickUpdate (100 nodes) ~8 KB ~1.2 KB 85%
TreeInit (50 nodes) ~15 KB ~3 KB 80%
Handshake ~200 bytes ~80 bytes 60%

3.7 Bandwidth Optimization

Delta Updates

By default, TickUpdate sends all node states. For high-frequency updates, use delta mode:

// Client-side: only send changed states
TickUpdate update;
update.is_delta = true;
update.states = GetChangedStatesOnly(); // Only nodes that changed

Monitor tracks last known state and applies deltas.

Batched Updates

For replay or recording, batch multiple ticks:

TickUpdateBatch batch;
batch.tree_id = "player_ai";
batch.ticks = CollectTicksForTimeRange(start, end);

4. EmberCore Extensions

Note: The code samples below were written during initial design. The actual implementation uses Ember::Network namespace for EmberCore network classes and Ember::Monitor namespace for EmberMonitor classes. Class name TCPConnectionManager was implemented as TCPServer in the Ember::Monitor namespace.

4.1 New Network Module Structure

EmberCore/
├── include/
│ └── Network/
│ ├── Schema/
│ │ ├── ember_protocol.fbs # Schema source file
│ │ └── generated/
│ │ └── ember_protocol_generated.h # Auto-generated C++
│ ├── Codec/
│ │ ├── MessageFrame.h # Frame structure
│ │ └── FlatBufferCodec.h # Encode/decode helpers
│ ├── Builder/
│ │ ├── TreeBuilder.h # Build tree from TreeInit
│ │ └── StateManager.h # Manage node states
│ └── Adapter/
│ └── TCPTreeAdapter.h # ITreeStructure for TCP
├── src/
│ └── Network/
│ ├── Codec/
│ │ └── FlatBufferCodec.cpp
│ ├── Builder/
│ │ ├── TreeBuilder.cpp
│ │ └── StateManager.cpp
│ └── Adapter/
│ └── TCPTreeAdapter.cpp
└── third_party/
└── flatbuffers/
└── flatbuffers.h # Header-only runtime (~50KB)

4.2 Key Classes

4.2.1 MessageFrame.h

#pragma once
#include <cstdint>
#include <vector>
namespace ember::network {
struct MessageFrame {
static constexpr size_t HEADER_SIZE = 5;
uint32_t payload_length;
std::vector<uint8_t> payload;
// Serialize frame to bytes
std::vector<uint8_t> ToBytes() const {
std::vector<uint8_t> result(HEADER_SIZE + payload.size());
// Little-endian length
result[0] = payload_length & 0xFF;
result[1] = (payload_length >> 8) & 0xFF;
result[2] = (payload_length >> 16) & 0xFF;
result[3] = (payload_length >> 24) & 0xFF;
// Message type
result[4] = static_cast<uint8_t>(message_type);
// Payload
std::copy(payload.begin(), payload.end(), result.begin() + HEADER_SIZE);
return result;
}
// Parse header from bytes (returns true if complete header available)
static bool ParseHeader(const uint8_t* data, size_t size,
uint32_t& out_length,
if (size < HEADER_SIZE) return false;
out_length = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
out_type = static_cast<Ember::Protocol::MessageType>(data[4]);
return true;
}
};
} // namespace ember::network

4.2.2 FlatBufferCodec.h

#pragma once
#include "MessageFrame.h"
#include "flatbuffers/flatbuffers.h"
#include <memory>
namespace ember::network {
class FlatBufferCodec {
public:
// ========================================================================
// Encoding (Building messages)
// ========================================================================
static MessageFrame BuildHandshake(
const std::string& version,
const std::string& client_id,
const std::string& client_name = "",
const std::vector<std::string>& capabilities = {});
static MessageFrame BuildHandshakeAck(
const std::string& version,
const std::string& session_id,
bool accepted,
const std::string& error = "");
static MessageFrame BuildTreeInit(
const std::string& tree_id,
const std::string& tree_name,
flatbuffers::FlatBufferBuilder& builder,
flatbuffers::Offset<Ember::Protocol::NodeDefinition> root);
static MessageFrame BuildTickUpdate(
const std::string& tree_id,
int64_t tick_number,
const std::vector<std::pair<int64_t, Ember::Protocol::NodeStatus>>& states,
const std::vector<int64_t>& execution_path = {},
bool is_delta = false);
static MessageFrame BuildError(
const std::string& message,
bool fatal = false);
// ========================================================================
// Decoding (Verifying and accessing messages)
// ========================================================================
template<typename T>
static bool Verify(const std::vector<uint8_t>& payload) {
flatbuffers::Verifier verifier(payload.data(), payload.size());
return verifier.VerifyBuffer<T>(nullptr);
}
template<typename T>
static const T* GetRoot(const std::vector<uint8_t>& payload) {
return flatbuffers::GetRoot<T>(payload.data());
}
template<typename T>
static const T* VerifyAndGetRoot(const std::vector<uint8_t>& payload) {
if (!Verify<T>(payload)) return nullptr;
return GetRoot<T>(payload);
}
};
} // namespace ember::network

4.2.3 TreeBuilder.h

#pragma once
#include "Core/Node.h"
#include <memory>
namespace ember::network {
class TreeBuilder {
public:
struct BuildResult {
bool success = false;
std::shared_ptr<BehaviorTree> tree;
int node_count = 0;
std::string error;
};
BuildResult Build(const Ember::Protocol::TreeInit* tree_init);
private:
std::unique_ptr<Node> BuildNode(
const Ember::Protocol::NodeDefinition* def,
Node* parent);
void BuildBlackboards(
BehaviorTree* tree,
const flatbuffers::Vector<
flatbuffers::Offset<Ember::Protocol::BlackboardDefinition>>* blackboards);
NodeType ConvertNodeType(Ember::Protocol::NodeType fb_type);
};
} // namespace ember::network

4.2.4 StateManager.h

#pragma once
#include <unordered_map>
#include <functional>
#include <mutex>
#include <vector>
namespace ember::network {
class StateManager {
public:
using NodeStatus = Ember::Protocol::NodeStatus;
using StateChangeCallback = std::function<void(int64_t node_id,
NodeStatus old_status,
NodeStatus new_status)>;
using TickCallback = std::function<void(int64_t tick_number)>;
explicit StateManager(std::shared_ptr<BehaviorTree> tree);
void ApplyTickUpdate(const Ember::Protocol::TickUpdate* update);
void ApplyBlackboardUpdate(const Ember::Protocol::BlackboardUpdate* update);
void Reset();
// State queries
NodeStatus GetNodeStatus(int64_t node_id) const;
int64_t GetCurrentTick() const { return current_tick_; }
const std::vector<int64_t>& GetExecutionPath() const { return execution_path_; }
// History (for timeline/replay)
struct TickSnapshot {
int64_t tick_number;
int64_t timestamp_ms;
std::unordered_map<int64_t, NodeStatus> states;
std::vector<int64_t> execution_path;
};
void EnableHistory(size_t max_ticks = 1000);
void DisableHistory();
const std::vector<TickSnapshot>& GetHistory() const { return history_; }
void SeekToTick(int64_t tick_number); // For replay
// Observers
void AddStateChangeCallback(StateChangeCallback callback);
void AddTickCallback(TickCallback callback);
private:
std::shared_ptr<BehaviorTree> tree_;
std::unordered_map<int64_t, NodeStatus> node_states_;
std::vector<int64_t> execution_path_;
int64_t current_tick_ = 0;
// History
bool history_enabled_ = false;
size_t max_history_ticks_ = 1000;
std::vector<TickSnapshot> history_;
// Observers
std::vector<StateChangeCallback> state_callbacks_;
std::vector<TickCallback> tick_callbacks_;
mutable std::mutex mutex_;
void NotifyStateChange(int64_t node_id, NodeStatus old_s, NodeStatus new_s);
void NotifyTick(int64_t tick_number);
};
} // namespace ember::network

4.2.5 TCPTreeAdapter.h

#pragma once
#include <memory>
#include <unordered_map>
namespace ember::network {
class TCPTreeAdapter : public ITreeStructure {
public:
using NodeStatus = Ember::Protocol::NodeStatus;
TCPTreeAdapter(std::shared_ptr<BehaviorTree> tree,
std::shared_ptr<StateManager> state_manager);
// ITreeStructure implementation
ITreeNode* GetRoot() override;
ITreeNode* FindNode(size_t id) override;
void TraverseDepthFirst(std::function<bool(ITreeNode*)> visitor) override;
void TraverseBreadthFirst(std::function<bool(ITreeNode*)> visitor) override;
bool Validate() const override;
size_t GetNodeCount() const override;
// TCP-specific extensions
NodeStatus GetNodeStatus(size_t node_id) const;
bool IsNodeInExecutionPath(size_t node_id) const;
int64_t GetCurrentTick() const;
// State manager access (for UI binding)
std::shared_ptr<StateManager> GetStateManager() { return state_manager_; }
private:
std::shared_ptr<BehaviorTree> tree_;
std::shared_ptr<StateManager> state_manager_;
std::unordered_map<size_t, std::unique_ptr<NodeAdapter>> adapters_;
NodeAdapter* root_adapter_ = nullptr;
void BuildAdapterCache();
NodeAdapter* GetOrCreateAdapter(Node* node);
};
} // namespace ember::network

5. Client Integration

5.1 Overview

To send behavior tree data to EmberMonitor, you generate protocol bindings for your language using the FlatBuffers compiler (flatc) and implement a simple TCP client wrapper. This approach:

  • Works with any language FlatBuffers supports (C++, C#, Python, Rust, Go, Java, TypeScript, Swift, etc.)
  • No external dependencies to maintain - you generate code once and own it
  • Full control over integration with your existing codebase
What you need:
1. ember_protocol.fbs # Schema file (from client_sdk/ or repository)
2. flatc compiler # One-time install to generate code
3. ~100 lines of code # TCP connection + message framing

5.2 Quick Start

Step 1: Install FlatBuffers Compiler

# Ubuntu/Debian
sudo apt install flatbuffers-compiler
# macOS
brew install flatbuffers
# Windows - download from https://github.com/google/flatbuffers/releases
# Add flatc.exe to PATH
# Verify
flatc --version

Step 2: Get the Schema

# Copy from client_sdk/ in the Ember repository
cp client_sdk/ember_protocol.fbs .

Step 3: Generate Code for Your Language

# C++
flatc --cpp -o generated/ ember_protocol.fbs
# C#
flatc --csharp -o Generated/ ember_protocol.fbs
# Python
flatc --python -o generated/ ember_protocol.fbs
# Other languages - see Section 5.3

Step 4: Add FlatBuffers Runtime

Language Runtime
C++ Header-only (included in client_sdk/cpp/)
C# NuGet: Google.FlatBuffers
Python pip install flatbuffers
Rust cargo add flatbuffers
Go go get github.com/google/flatbuffers/go
Java Maven: com.google.flatbuffers:flatbuffers-java
TypeScript npm install flatbuffers

Step 5: Implement Client Wrapper

You need to implement:

  1. TCP socket connection to EmberMonitor
  2. Message framing (5-byte header: 4-byte length + 1-byte type)
  3. Handshake → TreeInit → TickUpdate message sequence

See Section 5.4 for a complete C++ reference implementation.

5.3 Supported Languages

FlatBuffers supports code generation for many languages:

Language Command Runtime
C++ flatc --cpp Header-only
C# flatc --csharp NuGet: Google.FlatBuffers
Python flatc --python pip: flatbuffers
Rust flatc --rust crates.io: flatbuffers
Go flatc --go github.com/google/flatbuffers/go
Java flatc --java Maven: com.google.flatbuffers
Kotlin flatc --kotlin Maven: com.google.flatbuffers
TypeScript flatc --ts npm: flatbuffers
Swift flatc --swift SPM: FlatBuffers
Dart flatc --dart pub.dev: flat_buffers
Lua flatc --lua Community runtime
PHP flatc --php Composer

5.4 C++ Reference Client

A complete, working C++ reference client is provided in client_sdk/cpp/. This demonstrates:

  • Header-only implementation (EmberClient.h)
  • TCP socket connection (POSIX)
  • Message framing
  • Full protocol message sequence

Directory Structure:

client_sdk/
├── README.md # Integration guide
├── ember_protocol.fbs # Schema file
└── cpp/
├── EmberClient.h # Header-only reference client
├── flatbuffers/ # FlatBuffers runtime headers
└── example/
├── CMakeLists.txt
└── main.cpp # Minimal usage example

Basic Usage:

#include "EmberClient.h"
int main() {
ember::EmberClient client;
// Connect to EmberMonitor
if (!client.Connect("127.0.0.1", 12345)) {
return 1;
}
// Send tree structure (once at startup)
client.SendTreeInit("main_ai", BuildMyTree());
// Game loop - send tick updates
int64_t tick = 0;
while (running) {
myTree.Tick();
std::vector<ember::NodeStateInfo> states;
for (auto& node : myTree.GetNodes()) {
states.push_back({node.id, node.status});
}
client.SendTickUpdate("main_ai", tick++, states);
}
client.Disconnect();
return 0;
}

5.5 Integration Examples

C# (Unity)

using FlatBuffers;
using System.Net.Sockets;
public class EmberClient {
private TcpClient _client;
private NetworkStream _stream;
public void Connect(string host, int port) {
_client = new TcpClient(host, port);
_stream = _client.GetStream();
SendHandshake();
}
public void SendTickUpdate(string treeId, long tick, List<NodeState> states) {
var builder = new FlatBufferBuilder(1024);
// ... build TickUpdate message
SendFrame(MessageType.TickUpdate, builder.SizedByteArray());
}
private void SendFrame(MessageType type, byte[] payload) {
var header = new byte[5];
BitConverter.GetBytes((uint)payload.Length).CopyTo(header, 0);
header[4] = (byte)type;
_stream.Write(header, 0, 5);
_stream.Write(payload, 0, payload.Length);
}
}

Python

import socket
import struct
import flatbuffers
from ember.protocol import *
class EmberClient:
def __init__(self):
self.sock = None
self.tick = 0
def connect(self, host, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((host, port))
self._send_handshake()
def send_tick_update(self, tree_id, states):
builder = flatbuffers.Builder(1024)
# ... build TickUpdate message
self._send_frame(MessageType.TickUpdate, builder.Output())
self.tick += 1
def _send_frame(self, msg_type, payload):
header = struct.pack('<IB', len(payload), msg_type)
self.sock.sendall(header + bytes(payload))

Rust

use flatbuffers::FlatBufferBuilder;
use std::io::Write;
use std::net::TcpStream;
pub struct EmberClient {
stream: Option<TcpStream>,
}
impl EmberClient {
pub fn connect(&mut self, addr: &str) -> std::io::Result<()> {
self.stream = Some(TcpStream::connect(addr)?);
self.send_handshake()
}
fn send_frame(&mut self, msg_type: u8, payload: &[u8]) -> std::io::Result<()> {
if let Some(ref mut stream) = self.stream {
stream.write_all(&(payload.len() as u32).to_le_bytes())?;
stream.write_all(&[msg_type])?;
stream.write_all(payload)?;
}
Ok(())
}
}

5.6 Message Flow

Client EmberMonitor
│ │
│──────── Handshake ─────────────────►│
│◄─────── HandshakeAck ───────────────│
│ │
│──────── TreeInit ──────────────────►│ (send full tree structure)
│◄─────── TreeInitAck ────────────────│
│ │
│──────── TickUpdate ────────────────►│ ┐
│──────── TickUpdate ────────────────►│ │ repeat every tick
│──────── TickUpdate ────────────────►│ ┘
│ │
│──────── Disconnect ────────────────►│ (optional graceful close)
│ │

5.7 Troubleshooting

Problem Solution
Connection refused Ensure EmberMonitor is running and listening on the correct port
No tree appears Verify TreeInit message is sent before TickUpdates
Garbled data Check byte order (little-endian) and frame header format
flatc not found Add flatc to PATH or specify full path
Generated code errors Ensure flatc version matches runtime version

5.8 Integration Effort

Task Time
Install flatc 5 min
Generate code 5 min
Add runtime dependency 5 min
Implement client wrapper 1-2 hours
Total **~2 hours**

6. EmberMonitor Application

6.1 Application Structure

EmberMonitor/
├── CMakeLists.txt
├── include/
│ ├── App/
│ │ ├── MonitorApp.h
│ │ ├── MonitorFrame.h
│ │ └── MonitorConstants.h
│ ├── Network/
│ │ └── TCPServer.h
│ ├── Panels/
│ │ ├── ConnectionPanel.h
│ │ ├── MainPanel.h
│ │ └── RightSidePanel.h
│ ├── Tabs/
│ │ ├── MonitorNavigatorTab.h
│ │ └── PropertiesTab.h
│ └── Visualization/
│ └── MonitorTreeCanvas.h
└── src/
└── [corresponding .cpp files]

6.2 MonitorFrame Layout

┌──────────────────────────────────────────────────────────────────────────────┐
│ EmberMonitor [─][□][×] │
├──────────────────────────────────────────────────────────────────────────────┤
│ File View Connection Help │
├──────────────────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌───────────────────────────────────────┐ ┌──────────────┐ │
│ │ Connection │ │ ┌─────────────────┐ │ │ [Navigator] │ │
│ │ Panel │ │ │ Search Bar │ │ │ [Properties] │ │
│ ├──────────────┤ │ └─────────────────┘ │ ├──────────────┤ │
│ │ Port: 12345 │ │ │ │ ▼ Root │ │
│ │ [Start] │ │ │ │ ▼ Sequence │ │
│ │ │ │ Tree Visualization │ │ ● Act1 │ │
│ │ Status: │ │ │ │ ○ Act2 │ │
│ │ ● Connected │ │ (MainPanel with │ │ │ │
│ │ │ │ MonitorTreeCanvas) │ │ -- or -- │ │
│ │ Client: │ │ │ │ │ │
│ │ GameAI │ │ [Status overlays on nodes] │ │ Node: ... │ │
│ │ v1.0 │ │ │ │ Type: ... │ │
│ │ │ │ │ │ Status: │ │
│ │ │ │ │ │ RUNNING │ │
│ └──────────────┘ └───────────────────────────────────────┘ └──────────────┘ │
├──────────────────────────────────────────────────────────────────────────────┤
│ StatusBar: ● Connected | Tick Rate: 60/s | Latency: 2ms │
└──────────────────────────────────────────────────────────────────────────────┘

6.3 Key UI Components

6.3.1 StatusOverlay

Implementation note: The StatusOverlay class (including IStatusProvider interface and StatusColors) was implemented in EmberUI, not EmberMonitor. It lives in EmberUI/include/Visualization/StatusOverlay.h and is shared across applications. The original design below placed it in EmberMonitor, but during implementation it was generalized into the shared UI library.

// Original design sample (see EmberUI/include/Visualization/StatusOverlay.h
// for the actual implementation)
#pragma once
#include "Types/Color.h"
namespace ember::monitor {
class StatusOverlay {
public:
using NodeStatus = Ember::Protocol::NodeStatus;
struct StatusColors {
Color idle = Color(128, 128, 128); // Gray
Color running = Color(65, 105, 225); // Royal Blue
Color success = Color(50, 205, 50); // Lime Green
Color failure = Color(220, 20, 60); // Crimson
Color halted = Color(255, 165, 0); // Orange
};
// ... see EmberUI for actual implementation
};
} // namespace ember::monitor

6.3.2 ConnectionManager

Implementation note: The ConnectionManager class shown in the original design was not implemented as a separate class. Instead, MonitorFrame directly owns and manages a single TCPServer instance (in the Ember::Monitor namespace). The single-client connection model made a dedicated manager unnecessary — MonitorFrame handles server start/stop, connection events, and message dispatch directly.


7. Implementation Phases

Phase 1: Protocol Foundation (EmberCore)

Goal: Implement FlatBuffers protocol layer in EmberCore

Tasks:

  1. Add FlatBuffers header to third_party/
  2. Create ember_protocol.fbs schema
  3. Generate C++ code from schema
  4. Implement MessageFrame and FlatBufferCodec
  5. Write unit tests for encoding/decoding

Deliverables:

  • third_party/flatbuffers/flatbuffers.h
  • Network/Schema/ember_protocol.fbs
  • Unit tests for codec

Build-time dependency: flatc compiler (only needed to regenerate, generated files committed to repo)

Phase 2: Tree Building (EmberCore)

Goal: Build BehaviorTree from FlatBuffer messages

Tasks:

  1. Implement TreeBuilder to construct trees from TreeInit
  2. Implement StateManager for tick state tracking
  3. Implement TCPTreeAdapter as ITreeStructure implementation
  4. Write unit tests

Deliverables:

  • Unit tests

Dependencies: Phase 1

Phase 3: Client Integration

Goal: Provide schema and C++ reference client for user integration

Tasks:

  1. Create client_sdk/ directory with schema and README
  2. Implement C++ reference client (EmberClient.h)
  3. Create minimal usage example
  4. Update design doc Section 5 to focus on self-generation

Deliverables:

  • client_sdk/README.md - Integration guide
  • client_sdk/ember_protocol.fbs - Schema file
  • client_sdk/cpp/EmberClient.h - Header-only reference client
  • client_sdk/cpp/example/ - Minimal usage example
  • Updated Section 5 documentation

Note: Users generate bindings for their language using flatc. We provide C++ as reference only.

Dependencies: Phase 1

Phase 4: Basic Monitor Application

Goal: Create minimal working EmberMonitor application

Tasks:

  1. Set up EmberMonitor CMake project
  2. Create MonitorApp and MonitorFrame
  3. Implement basic TCPServer
  4. Create ConnectionPanel with server start/stop
  5. Display connected clients

Deliverables:

  • EmberMonitor/CMakeLists.txt

Dependencies: Phase 2

Phase 5: Tree Visualization

Goal: Visualize received trees with status overlays

Tasks:

  1. Create MonitorMainPanel with scene container
  2. Adapt TreeVisualization for read-only mode
  3. Implement StatusOverlay for node status colors
  4. Implement ExecutionPathOverlay for path highlighting
  5. Create MonitorHierarchyTab (read-only)
  6. Create MonitorPropertiesTab (read-only with status)

Deliverables:

  • MonitorMainPanel.h/.cpp
  • ExecutionPathOverlay.h/.cpp
  • MonitorHierarchyTab.h/.cpp
  • MonitorPropertiesTab.h/.cpp

Dependencies: Phase 4

Phase 6: Live Updates

Goal: Real-time tick updates and blackboard monitoring

Tasks:

  1. Connect TickUpdate handling to UI refresh
  2. Implement BlackboardTab for live blackboard view
  3. Add tick counter and rate display to status bar
  4. Implement latency measurement
  5. Optimize refresh rate for high-frequency updates

Deliverables:

  • Tick update → UI refresh pipeline
  • BlackboardTab.h/.cpp
  • Status bar with tick info
  • Latency display

Dependencies: Phase 5

Phase 7: Timeline and History

Goal: Tick history with timeline scrubbing

Tasks:

  1. Enable history recording in StateManager
  2. Implement TickTimelineTab with scrubber
  3. Add play/pause/step controls
  4. Implement tick replay at variable speeds
  5. Add visual markers for state changes

Deliverables:

  • TickTimelineTab.h/.cpp
  • Replay functionality
  • Timeline markers

Dependencies: Phase 6

Phase 8: Polish and Integration

Goal: Production-ready application

Tasks:

  1. Implement MonitorPreferences dialog
  2. Add keyboard shortcuts
  3. Implement reconnection logic
  4. Add export features (tick log to file)
  5. Performance optimization
  6. Cross-platform testing

Deliverables:

  • Preferences dialog
  • Keyboard shortcuts
  • Auto-reconnect
  • Export functionality
  • User documentation

Dependencies: Phase 7


8. CMake Structure

8.1 Root CMakeLists.txt Updates

# Add to existing BTV/CMakeLists.txt
option(BUILD_EMBER_MONITOR "Build EmberMonitor application" ON)
option(BUILD_CLIENT_SDK "Build client SDK examples" OFF)
# ... existing EmberCore and EmberForge ...
if(BUILD_EMBER_MONITOR)
add_subdirectory(EmberMonitor)
endif()
if(BUILD_CLIENT_SDK)
add_subdirectory(client_sdk/cpp)
endif()

8.2 EmberCore CMakeLists.txt Updates

# Add FlatBuffers schema generation (optional, only if regenerating)
find_program(FLATC flatc)
if(FLATC)
set(FLATBUFFERS_SCHEMA
${CMAKE_CURRENT_SOURCE_DIR}/include/Network/Schema/ember_protocol.fbs)
set(FLATBUFFERS_GENERATED
${CMAKE_CURRENT_SOURCE_DIR}/include/Network/Schema/generated/ember_protocol_generated.h)
add_custom_command(
OUTPUT ${FLATBUFFERS_GENERATED}
COMMAND ${FLATC} --cpp
-o ${CMAKE_CURRENT_SOURCE_DIR}/include/Network/Schema/generated/
${FLATBUFFERS_SCHEMA}
DEPENDS ${FLATBUFFERS_SCHEMA}
COMMENT "Generating FlatBuffers C++ code"
)
add_custom_target(generate_flatbuffers DEPENDS ${FLATBUFFERS_GENERATED})
else()
message(STATUS "flatc not found - using pre-generated FlatBuffers code")
endif()
# Add Network module sources
set(NETWORK_SOURCES
src/Network/Codec/FlatBufferCodec.cpp
src/Network/Builder/TreeBuilder.cpp
src/Network/Builder/StateManager.cpp
src/Network/Adapter/TCPTreeAdapter.cpp
)
set(NETWORK_HEADERS
include/Network/Schema/ember_protocol.fbs
include/Network/Schema/generated/ember_protocol_generated.h
include/Network/Codec/MessageFrame.h
include/Network/Codec/FlatBufferCodec.h
include/Network/Builder/TreeBuilder.h
include/Network/Builder/StateManager.h
include/Network/Adapter/TCPTreeAdapter.h
)
# Add to EmberCore target
target_sources(EmberCore PRIVATE ${NETWORK_SOURCES} ${NETWORK_HEADERS})
# Include FlatBuffers header-only runtime
target_include_directories(EmberCore PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/third_party/flatbuffers
)

8.3 EmberMonitor CMakeLists.txt

cmake_minimum_required(VERSION 3.16)
project(EmberMonitor VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find wxWidgets
find_package(wxWidgets REQUIRED COMPONENTS core base aui net)
include(${wxWidgets_USE_FILE})
# Source files
set(MONITOR_SOURCES
src/App/MonitorApp.cpp
src/App/MonitorFrame.cpp
src/Network/TCPServer.cpp
src/Network/ConnectionManager.cpp
src/Panels/ConnectionPanel.cpp
src/Panels/MonitorMainPanel.cpp
src/Panels/StatusPanel.cpp
src/Tabs/ConnectionListTab.cpp
src/Tabs/MonitorHierarchyTab.cpp
src/Tabs/MonitorPropertiesTab.cpp
src/Tabs/BlackboardTab.cpp
src/Tabs/TickTimelineTab.cpp
src/Visualization/StatusOverlay.cpp
src/Visualization/ExecutionPathOverlay.cpp
src/Utils/MonitorPreferences.cpp
)
# Create executable
add_executable(EmberMonitor ${MONITOR_SOURCES})
target_include_directories(EmberMonitor
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/EmberCore/include
${CMAKE_SOURCE_DIR}/EmberForge/include
)
target_link_libraries(EmberMonitor
PRIVATE
EmberCore
${wxWidgets_LIBRARIES}
)
# Platform-specific
if(WIN32)
set_target_properties(EmberMonitor PROPERTIES WIN32_EXECUTABLE TRUE)
endif()
if(APPLE)
set_target_properties(EmberMonitor PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/resources/Info.plist
)
endif()
# Install
install(TARGETS EmberMonitor
RUNTIME DESTINATION bin
BUNDLE DESTINATION .
)

9. Dependencies Summary

9.1 Build-Time Dependencies

Component Dependency Required? Notes
EmberCore flatc No Only if regenerating schema
EmberMonitor wxWidgets (net) Yes Socket support
Client SDK flatc No Pre-generated code committed

9.2 Runtime Dependencies

Component Dependency Notes
EmberCore None FlatBuffers is header-only
EmberMonitor None All compiled in
C++ Client None Header-only
C# Client None Self-contained
Python Client flatbuffers pip package Or bundle locally

9.3 Third-Party Files to Add

EmberCore/
└── third_party/
└── flatbuffers/
└── flatbuffers.h # ~50KB, header-only runtime
# Download from: github.com/google/flatbuffers

10. Testing Strategy

10.1 Unit Tests

Component Test Focus
FlatBufferCodec Encode/decode all message types, verify round-trip
TreeBuilder Build valid trees, handle malformed data
StateManager State transitions, history, delta updates
TCPTreeAdapter ITreeStructure compliance, state queries

10.2 Integration Tests

Test Description
Protocol handshake Full handshake sequence with FlatBuffers
Tree construction TreeInit → visualization
Live updates TickUpdate → UI refresh at 60Hz
Reconnection Handle disconnect/reconnect

10.3 Mock Client

class MockBTClient {
public:
void Connect(const std::string& host, int port);
void SendMockTree(int node_count);
void SimulateTicks(int count, int interval_ms);
void SimulateRandomStates();
void SimulateDisconnect();
};

10.4 Performance Tests

Metric Target Notes
Tick update latency < 1ms FlatBuffers zero-copy helps
UI refresh rate 60 FPS During high-frequency updates
Memory (1000 nodes) < 30 MB Reduced vs JSON
Network bandwidth < 50 KB/s At 60Hz, 100 nodes

11. Future Considerations

11.1 Potential Extensions

Feature Description
Breakpoints Pause execution at specific nodes
Remote Control Send commands back to client
Recording Save tick history to file
Multiple Trees Monitor multiple trees per client
Filtering Show/hide specific node types
Alerts Notifications for conditions
Statistics Execution frequency, duration

11.2 Protocol Versioning

  • Version negotiated in Handshake
  • FlatBuffers supports schema evolution (add fields, deprecate)
  • Maintain backward compatibility for minor versions

11.3 Security Considerations

For production use:

  • TLS encryption via wxWidgets SSL sockets
  • Authentication tokens in Handshake
  • Rate limiting
  • Connection whitelisting

12. Glossary

Term Definition
Tick Single execution cycle of the behavior tree
Node Status Current execution state (IDLE, RUNNING, SUCCESS, FAILURE, HALTED)
Execution Path Ordered list of nodes executed in current tick
Blackboard Shared key-value storage for tree nodes
Delta Update Tick update containing only changed states
Tree Init Message containing full tree structure
FlatBuffers Binary serialization library (Google)
Zero-Copy Access data directly from buffer without parsing

13. References