Client Data Areas (Manager)
The manager package exposes the full SimConnect Client Data Area (CDA) API as direct methods on the Manager interface. This guide explains how to use CDAs through the manager’s lifecycle-managed connection, covering both the reader and writer roles.
See also: Client Data Areas for the engine-layer reference covering types, constants, and low-level usage.
Overview
Client data areas are named shared memory regions that SimConnect add-ons use to exchange arbitrary structured data. One add-on creates the area and writes to it; any other connected client can map the same name, define the layout, and subscribe to updates.
Typical use cases:
- A weather injector writes current conditions; a cockpit display reads and renders them.
- A flight data recorder writes position and attitude; a telemetry exporter reads in real time.
- Two independent add-ons share a control channel without a network connection.
The manager wraps the same underlying SimConnect calls as the engine client. The key difference is that each method checks whether the manager is currently connected and returns ErrNotConnected if not. There is no automatic retry or queuing — callers are responsible for registering CDAs at the right time in the connection lifecycle.
MapClientDataNameToID is also part of the manager interface and is the prerequisite for all CDA operations. Call it first after connection before calling any other CDA method.
Prerequisites
Before calling any CDA method:
- The manager must be connected to the simulator. Use
OnConnectionStateChangeorSubscribeOnOpento detect when the connection is ready. - Choose define IDs and request IDs in the user range. See Request and ID Management for the ID ranges. All user IDs must be between 1 and 999,999,849.
- The
requestIDpassed toRequestClientDataidentifies responses in the dispatch loop. It must be unique within your application and within the user range.
Workflow
The following steps apply to any CDA integration through the manager.
Step 1 — Map the area name to an ID
Both the writer and the reader call MapClientDataNameToID with the same name string. This is what links the two clients together.
const (
WeatherAreaID uint32 = 1000
WeatherDefID uint32 = 1001
WeatherReqID uint32 = 1002
)
mgr.OnConnectionStateChange(func(old, new manager.ConnectionState) {
if new != manager.StateConnected {
return
}
if err := mgr.MapClientDataNameToID("MyAddon.Weather", WeatherAreaID); err != nil {
log.Printf("MapClientDataNameToID failed: %v", err)
}
})
Step 2 — Create the area (writer only)
The client that owns the area calls CreateClientData. Readers skip this step.
// dwSize must be between 1 and 8192 bytes
if err := mgr.CreateClientData(WeatherAreaID, 16, types.SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); err != nil {
log.Printf("CreateClientData failed: %v", err)
}
Step 3 — Define the struct layout
Call AddToClientDataDefinition once per field to describe the memory layout. Each call maps a byte offset and size to a definition ID.
// Field 1: float64 at offset 0 (8 bytes)
mgr.AddToClientDataDefinition(
WeatherDefID,
0, // byte offset
uint32(types.SIMCONNECT_CLIENTDATATYPE_FLOAT64), // typed size constant
0, // epsilon
0, // datum ID
)
// Field 2: float64 at offset 8 (8 bytes)
mgr.AddToClientDataDefinition(
WeatherDefID,
8,
uint32(types.SIMCONNECT_CLIENTDATATYPE_FLOAT64),
0,
0,
)
Step 4 — Subscribe to updates (reader)
Call RequestClientData to register a periodic delivery subscription. Responses arrive as SIMCONNECT_RECV_ID_CLIENT_DATA messages.
if err := mgr.RequestClientData(
WeatherAreaID,
WeatherReqID,
WeatherDefID,
types.SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET, // deliver on each write
types.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
0, 0, 0,
); err != nil {
log.Printf("RequestClientData failed: %v", err)
}
Step 5 — Receive updates
Use Subscribe or SubscribeWithFilter to receive SIMCONNECT_RECV_ID_CLIENT_DATA messages:
sub := mgr.SubscribeWithFilter("weather-cda", 20, func(msg engine.Message) bool {
return types.SIMCONNECT_RECV_ID(msg.DwID) == types.SIMCONNECT_RECV_ID_CLIENT_DATA
})
defer sub.Unsubscribe()
go func() {
for {
select {
case msg := <-sub.Messages():
cd := msg.AsClientData()
if cd == nil || cd.DwRequestID != WeatherReqID {
continue
}
weather := engine.CastDataAs[SharedWeather](&cd.DwData)
log.Printf("Temperature: %.1f°C Pressure: %.2f hPa",
weather.TemperatureC, weather.PressureHPa)
case <-sub.Done():
return
}
}
}()
Step 6 — Write data (writer)
The owning add-on calls SetClientData whenever the data changes:
data := SharedWeather{TemperatureC: 15.5, PressureHPa: 1013.25}
if err := mgr.SetClientData(
WeatherAreaID,
WeatherDefID,
0, // flags — always 0
0, // dwReserved — must be 0
uint32(unsafe.Sizeof(data)),
unsafe.Pointer(&data),
); err != nil {
log.Printf("SetClientData failed: %v", err)
}
Step 7 — Clean up
When a definition is no longer needed, call ClearClientDataDefinition to release it:
if err := mgr.ClearClientDataDefinition(WeatherDefID); err != nil {
log.Printf("ClearClientDataDefinition failed: %v", err)
}
Note: There is no
DeleteClientDatacall in the SimConnect SDK. The area itself is released automatically when the owning client disconnects from the simulator.
Complete Example
The following snippet shows a reader that connects via manager, maps an existing shared area, defines a two-field layout, and prints every update.
//go:build windows
package main
import (
"errors"
"log"
"os"
"os/signal"
"unsafe"
"github.com/mrlm-net/simconnect"
"github.com/mrlm-net/simconnect/pkg/engine"
"github.com/mrlm-net/simconnect/pkg/manager"
"github.com/mrlm-net/simconnect/pkg/types"
)
type SharedWeather struct {
TemperatureC float64 // offset 0, 8 bytes
PressureHPa float64 // offset 8, 8 bytes
}
const (
WeatherAreaID uint32 = 1000
WeatherDefID uint32 = 1001
WeatherReqID uint32 = 1002
)
func main() {
mgr := simconnect.New("WeatherReader",
manager.WithAutoReconnect(true),
)
mgr.OnConnectionStateChange(func(old, new manager.ConnectionState) {
if new != manager.StateConnected {
return
}
setupCDA(mgr)
})
sub := mgr.SubscribeWithFilter("weather-cda", 20, func(msg engine.Message) bool {
return types.SIMCONNECT_RECV_ID(msg.DwID) == types.SIMCONNECT_RECV_ID_CLIENT_DATA
})
defer sub.Unsubscribe()
go func() {
for {
select {
case msg := <-sub.Messages():
cd := msg.AsClientData()
if cd == nil || cd.DwRequestID != WeatherReqID {
continue
}
weather := engine.CastDataAs[SharedWeather](&cd.DwData)
log.Printf("Temperature: %.1f°C Pressure: %.2f hPa",
weather.TemperatureC, weather.PressureHPa)
case <-sub.Done():
return
}
}
}()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)
go func() {
<-sigChan
mgr.Stop()
}()
if err := mgr.Start(); err != nil {
log.Printf("Manager stopped: %v", err)
}
}
func setupCDA(mgr manager.Manager) {
if err := mgr.MapClientDataNameToID("MyAddon.Weather", WeatherAreaID); err != nil {
log.Printf("MapClientDataNameToID: %v", err)
return
}
// Reader skips CreateClientData — only the writer calls it.
if err := mgr.AddToClientDataDefinition(WeatherDefID, 0,
uint32(types.SIMCONNECT_CLIENTDATATYPE_FLOAT64), 0, 0); err != nil {
log.Printf("AddToClientDataDefinition [0]: %v", err)
}
if err := mgr.AddToClientDataDefinition(WeatherDefID, 8,
uint32(types.SIMCONNECT_CLIENTDATATYPE_FLOAT64), 0, 0); err != nil {
log.Printf("AddToClientDataDefinition [8]: %v", err)
}
if err := mgr.RequestClientData(
WeatherAreaID, WeatherReqID, WeatherDefID,
types.SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET,
types.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
0, 0, 0,
); err != nil {
if errors.Is(err, manager.ErrNotConnected) {
log.Println("Not connected, skipping RequestClientData")
} else {
log.Printf("RequestClientData: %v", err)
}
}
}
Method Reference
| Method | Parameters | Returns | Notes |
|---|---|---|---|
MapClientDataNameToID |
clientDataName string, clientDataID uint32 |
error |
Called by both reader and writer; must be called first after connection |
CreateClientData |
clientDataID uint32, dwSize uint32, flags SIMCONNECT_CREATE_CLIENT_DATA_FLAG |
error |
Writer only; dwSize must be 1–8192 bytes |
AddToClientDataDefinition |
defineID uint32, dwOffset uint32, dwSizeOrType uint32, epsilon float32, datumID uint32 |
error |
Call once per field; use SIMCONNECT_CLIENTDATATYPE_* constants for dwSizeOrType |
RequestClientData |
clientDataID uint32, requestID uint32, defineID uint32, period SIMCONNECT_CLIENT_DATA_PERIOD, flags SIMCONNECT_CLIENT_DATA_REQUEST_FLAG, origin uint32, interval uint32, limit uint32 |
error |
Reader only; requestID must be in user range |
SetClientData |
clientDataID uint32, defineID uint32, flags uint32, dwReserved uint32, cbUnitSize uint32, data unsafe.Pointer |
error |
Writer only; flags and dwReserved must both be 0 |
ClearClientDataDefinition |
defineID uint32 |
error |
Removes all field definitions for the given define ID |
MapClientDataNameToID is defined directly on the Manager interface alongside the CDA methods. All six methods are available without calling mgr.Client().
ErrNotConnected
Every method in this table returns manager.ErrNotConnected if called when the manager has no active connection. This includes the period between startup and the first successful connection, and any reconnection gap when auto-reconnect is enabled.
if err := mgr.CreateClientData(...); err != nil {
if errors.Is(err, manager.ErrNotConnected) {
// Not yet connected — defer setup to the OnConnectionStateChange handler
return
}
log.Printf("CreateClientData failed: %v", err)
}
The manager does not queue or retry failed calls. Register your CDA setup inside OnConnectionStateChange or SubscribeOnOpen so it runs automatically on each (re)connection.
ID Range
The requestID parameter in RequestClientData must be within the user range: 1 to 999,999,849. The manager reserves 999,999,850–999,999,999 for internal use.
Use manager.IsValidUserID(id) to validate an ID before use:
const WeatherReqID uint32 = 1002
if !manager.IsValidUserID(WeatherReqID) {
log.Fatal("ID conflicts with manager reserved range")
}
See Request and ID Management for the complete ID allocation table and validation helpers.
See Also
- Client Data Areas — Engine-layer reference: types, constants (
SIMCONNECT_CLIENTDATATYPE_*,SIMCONNECT_CLIENT_DATA_PERIOD,SIMCONNECT_CLIENT_DATA_REQUEST_FLAG), and complete writer/reader examples - Manager Usage — Full manager API reference including subscriptions and connection lifecycle
- Request and ID Management — ID allocation strategy and conflict prevention