#pragma once
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
// ── Bridge header (64 bytes) ───────────────────────────────────────────────
struct BridgeHeader {
char magic[4]; // "BGOD"
uint16_t version;
uint8_t direction; // 0 = godot→blender, 1 = blender→godot
uint8_t _pad1;
uint64_t sequence;
uint32_t object_count;
uint32_t _pad2;
uint64_t frame_number;
uint8_t _reserved[32];
};
static_assert(sizeof(BridgeHeader) == 64, "BridgeHeader must be 64 bytes");
// ── Object types ───────────────────────────────────────────────────────────
enum ObjectType : uint8_t {
OBJ_MESH = 0,
OBJ_LIGHT = 1,
OBJ_CAMERA = 2,
OBJ_EMPTY = 3,
};
// ── Light subtypes ─────────────────────────────────────────────────────────
enum LightSubType : uint8_t {
LIGHT_POINT = 0,
LIGHT_SUN = 1, // DirectionalLight3D ↔ Blender "SUN"
LIGHT_SPOT = 2,
};
// ── Update types ───────────────────────────────────────────────────────────
enum UpdateType : uint8_t {
UPDATE_TRANSFORM = 0,
UPDATE_VERTEX = 1,
UPDATE_MATERIAL = 2,
UPDATE_FULL = 3,
UPDATE_DELETE = 4,
};
// ── Object table entry (32 bytes) ──────────────────────────────────────────
struct ObjectEntry {
uint32_t object_id;
uint8_t object_type;
uint8_t update_type;
uint8_t sub_type; // light subtype (LightSubType) or 0
uint8_t _pad;
uint32_t data_offset;
uint32_t data_size;
uint32_t sequence;
uint8_t _reserved[12];
};
static_assert(sizeof(ObjectEntry) == 32, "ObjectEntry must be 32 bytes");
// ── Layout constants ───────────────────────────────────────────────────────
constexpr size_t HEADER_OFFSET = 0;
constexpr size_t OBJECT_TABLE_OFFSET = sizeof(BridgeHeader); // 64
// ── Read/write helpers ─────────────────────────────────────────────────────
inline void write_header(void *base, const BridgeHeader &hdr) {
memcpy(base, &hdr, sizeof(BridgeHeader));
}
inline BridgeHeader read_header(const void *base) {
BridgeHeader hdr;
memcpy(&hdr, base, sizeof(BridgeHeader));
return hdr;
}
inline void write_object_entry(void *base, uint32_t index, const ObjectEntry &entry) {
uint8_t *p = static_cast<uint8_t *>(base) + OBJECT_TABLE_OFFSET + index * sizeof(ObjectEntry);
memcpy(p, &entry, sizeof(ObjectEntry));
}
inline ObjectEntry read_object_entry(const void *base, uint32_t index) {
ObjectEntry entry;
const uint8_t *p = static_cast<const uint8_t *>(base) + OBJECT_TABLE_OFFSET + index * sizeof(ObjectEntry);
memcpy(&entry, p, sizeof(ObjectEntry));
return entry;
}
// Data blob offset: past header + object table
inline size_t data_blob_offset(uint32_t max_objects) {
return OBJECT_TABLE_OFFSET + max_objects * sizeof(ObjectEntry);
}
// ── Split-region helpers ──────────────────────────────────────────────────
// SHM layout (3 zones):
// Region 0 [0, REGION_SIZE): Godot→Blender
// Region 1 [REGION_SIZE, 2*REGION_SIZE): Blender→Godot
// Tile ring [2*REGION_SIZE, SHM_SIZE): Texture streaming
// Each region has its own header, object table, and data blob area.
constexpr size_t REGION_SIZE = 8 * 1024 * 1024; // 8 MB default per region
inline void *region_base(void *shm, int region, size_t region_size = REGION_SIZE) {
return static_cast<uint8_t *>(shm) + region * region_size;
}
// ── POSIX shared memory wrapper ────────────────────────────────────────────
class ShmRegion {
public:
ShmRegion() = default;
~ShmRegion() { destroy(); }
// Non-copyable
ShmRegion(const ShmRegion &) = delete;
ShmRegion &operator=(const ShmRegion &) = delete;
// Create a new shared memory region (owner — will unlink on destroy)
bool create(const char *name, size_t size) {
strncpy(_name, name, sizeof(_name) - 1);
_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
if (_fd < 0) {
return false;
}
if (ftruncate(_fd, size) < 0) {
close(_fd);
_fd = -1;
return false;
}
_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
if (_ptr == MAP_FAILED) {
_ptr = nullptr;
close(_fd);
_fd = -1;
return false;
}
_size = size;
_owner = true;
memset(_ptr, 0, size);
return true;
}
// Open an existing shared memory region (reader — will NOT unlink)
bool open(const char *name, size_t size) {
strncpy(_name, name, sizeof(_name) - 1);
_fd = shm_open(name, O_RDWR, 0666);
if (_fd < 0) {
return false;
}
_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
if (_ptr == MAP_FAILED) {
_ptr = nullptr;
close(_fd);
_fd = -1;
return false;
}
_size = size;
_owner = false;
return true;
}
// Grow the shared memory and remap (called when Blender sends shm_resized)
bool resize(size_t new_size) {
if (_fd < 0 || !_ptr)
return false;
// Grow the backing file
if (ftruncate(_fd, new_size) < 0)
return false;
// Unmap old
munmap(_ptr, _size);
// Remap at new size
_ptr = mmap(nullptr, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
if (_ptr == MAP_FAILED) {
_ptr = nullptr;
_size = 0;
return false;
}
_size = new_size;
return true;
}
void destroy() {
if (_ptr) {
munmap(_ptr, _size);
_ptr = nullptr;
}
if (_fd >= 0) {
close(_fd);
_fd = -1;
}
if (_owner && _name[0]) {
shm_unlink(_name);
_name[0] = 0;
}
_size = 0;
}
void *ptr() const { return _ptr; }
size_t size() const { return _size; }
bool valid() const { return _ptr != nullptr; }
private:
void *_ptr = nullptr;
size_t _size = 0;
int _fd = -1;
char _name[256] = {};
bool _owner = false;
};