OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge / src / shm_protocol.h

210 lines · c ref:
#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;
};