--- name: subspace-clients description: Write Subspace clients in C++, Python, Rust, or Java. Use when the user asks how to connect to Subspace, create publishers or subscribers, publish messages, read messages, or build language client libraries with Bazel, CMake, or Android Soong/Blueprint. --- # Subspace Clients ## Core Model - A `subspace_server` process must be running before clients connect. - Non-Android default socket: `/tmp/subspace`. - Android default socket: `/data/local/tmp/subspace`. - A publisher creates a channel with a slot size and slot count, writes into a shared-memory message buffer, then publishes the written byte count. - A subscriber attaches to a channel, waits or polls for data, then reads until the API reports no more messages. - User-facing build examples use `bazel`; `bazelisk` is a drop-in replacement when `bazel` is not available. ## C++ Client Include `client/client.h` and link `//client:subspace_client` or the CMake `subspace_client` target. Use a `subspace::Client` when one process needs to create multiple publishers or subscribers that share one server connection. If the publisher or subscriber can own its own connection, use the convenience free functions: ```cpp auto pub_or = subspace::CreatePublisher( "example", subspace::PublisherOptions().SetSlotSize(256).SetNumSlots(8), "/tmp/subspace", "cpp-publisher"); auto sub_or = subspace::CreateSubscriber( "example", subspace::SubscriberOptions(), "/tmp/subspace", "cpp-subscriber"); ``` Publish: ```cpp #include "client/client.h" #include subspace::Client client; auto status = client.Init("/tmp/subspace", "cpp-publisher"); if (!status.ok()) throw std::runtime_error(status.ToString()); auto pub_or = client.CreatePublisher("example", 256, 8); if (!pub_or.ok()) throw std::runtime_error(pub_or.status().ToString()); subspace::Publisher pub = *std::move(pub_or); auto buffer_or = pub.GetMessageBuffer(); if (!buffer_or.ok() || *buffer_or == nullptr) { throw std::runtime_error("no publisher buffer available"); } const char payload[] = "hello from cpp"; std::memcpy(*buffer_or, payload, sizeof(payload) - 1); auto msg_or = pub.PublishMessage(sizeof(payload) - 1); if (!msg_or.ok()) throw std::runtime_error(msg_or.status().ToString()); ``` Subscribe: ```cpp subspace::Client client; auto status = client.Init("/tmp/subspace", "cpp-subscriber"); if (!status.ok()) throw std::runtime_error(status.ToString()); auto sub_or = client.CreateSubscriber("example"); if (!sub_or.ok()) throw std::runtime_error(sub_or.status().ToString()); subspace::Subscriber sub = *std::move(sub_or); status = sub.Wait(); if (!status.ok()) throw std::runtime_error(status.ToString()); for (;;) { auto msg_or = sub.ReadMessage(); if (!msg_or.ok()) throw std::runtime_error(msg_or.status().ToString()); if (msg_or->length == 0) break; auto bytes = static_cast(msg_or->buffer); // Process bytes[0..msg_or->length). } // Leave the subscriber trigger fd unread: // sub.ReadMessage(subspace::ReadMode::kReadNext, // subspace::ClearTrigger::kNoClearTrigger); ``` ## Python Client The Python binding is `client.python.subspace`. The server binding used by tests is `server.python.subspace_server`. ```python import client.python.subspace as subspace client = subspace.Client() client.init(server_socket="/tmp/subspace", client_name="python-client") publisher = client.create_publisher( channel_name="example", slot_size=256, num_slots=8 ) subscriber = client.create_subscriber(channel_name="example") publisher.publish_message(b"hello from python") subscriber.wait() while True: data = subscriber.read_message() if len(data) == 0: break # Process data as bytes. # Leave the subscriber trigger fd unread: # subscriber.read_message(clear_trigger=subspace.ClearTrigger.NO_CLEAR_TRIGGER) ``` For message metadata and ordinals, use `read_message_object()`: ```python with subscriber.read_message_object() as msg: print(msg.ordinal, msg.timestamp, msg.buffer) ``` For multiple in-flight unpublished slots, configure and use explicit leases: ```python options = subspace.PublisherOptions() options.set_slot_size(4096) options.set_num_slots(16) options.set_max_outstanding_slot_leases(3) publisher = client.create_publisher("camera", options=options) lease = publisher.acquire_buffer_lease() if lease is not None: lease.buffer[:len(payload)] = payload publisher.publish_buffer_lease(lease, len(payload)) ``` `release_buffer_lease(lease)` discards an unpublished lease, while `reclaim_buffer_lease(slot_id)` reacquires a retired slot. Use `SubscriberOptions.set_max_subscribers(n)` to establish a server-enforced channel subscriber limit. ## Rust Client The Rust crate name is `subspace_client`. ```rust use subspace_client::{ClearTrigger, Client, PublisherOptions, ReadMode, SubscriberOptions}; let client = Client::new("/tmp/subspace", "rust-client").unwrap(); let pub_opts = PublisherOptions::new() .set_slot_size(256) .set_num_slots(8); let publisher = client.create_publisher("example", &pub_opts).unwrap(); let payload = b"hello from rust"; let (buf_ptr, _capacity) = publisher .get_message_buffer(payload.len() as i64) .unwrap() .unwrap(); unsafe { std::ptr::copy_nonoverlapping(payload.as_ptr(), buf_ptr, payload.len()); } publisher.publish_message(payload.len() as i64).unwrap(); let sub_opts = SubscriberOptions::new(); let subscriber = client.create_subscriber("example", &sub_opts).unwrap(); subscriber.wait(Some(5000)).unwrap(); loop { let msg = subscriber.read_message(ReadMode::ReadNext).unwrap(); if msg.is_empty() { break; } let data = unsafe { msg.as_slice() }; // Process data. } // Leave the subscriber trigger fd unread: // subscriber.read_message_with_trigger(ReadMode::ReadNext, ClearTrigger::NoClearTrigger); ``` For multiple in-flight unpublished slots: ```rust let options = PublisherOptions::new() .set_slot_size(4096) .set_num_slots(16) .set_max_outstanding_slot_leases(3); let publisher = client.create_publisher("camera", &options).unwrap(); if let Some(mut lease) = publisher.acquire_buffer_lease().unwrap() { unsafe { lease.as_mut_slice()[..payload.len()].copy_from_slice(payload); } publisher .publish_buffer_lease(&lease, payload.len() as i64) .unwrap(); } ``` Rust also exposes `release_buffer_lease`, `reclaim_buffer_lease`, lease-scoped metadata, `set_notify_retirement_on_forced_reuse`, and `SubscriberOptions::set_max_subscribers`. ## Java Client The Java client is Android-focused and uses JNI. Load `libsubspace_jni.so` normally via `System.loadLibrary("subspace_jni")`; device-side tests may preload native libraries with absolute paths when using `/data/local/tmp`. ```java import com.subspace.SubspaceClient; import com.subspace.SubspaceMessage; import com.subspace.SubspacePublisher; import com.subspace.SubspaceSubscriber; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; byte[] payload = "hello from java".getBytes(StandardCharsets.UTF_8); try (SubspaceClient client = new SubspaceClient("/data/local/tmp/subspace", "java-client"); SubspacePublisher publisher = client.createPublisher("/example", 256, 8); SubspaceSubscriber subscriber = client.createSubscriber("/example")) { ByteBuffer buffer = publisher.getMessageBuffer(payload.length); if (buffer == null) { throw new IllegalStateException("no publisher buffer available"); } buffer.put(payload); publisher.publishMessage(payload.length); SubspaceMessage message = subscriber.readMessage(); if (message != null) { ByteBuffer data = message.getData(); // Process data[0..message.getLength()). } } ``` ## Build The Clients C++: ```bash bazel build //client:subspace_client cmake -S . -B build/cmake-Debug -DCMAKE_BUILD_TYPE=Debug cmake --build build/cmake-Debug --target subspace_client ``` Python: ```bash bazel build //client/python:subspace bazel test //client/python:client_test cmake -S . -B build/cmake-Debug -DCMAKE_BUILD_TYPE=Debug cmake --build build/cmake-Debug --target subspace ``` Rust: ```bash bazel build //rust_client:subspace_client_rust bazel test //rust_client:client_test cmake -S . -B build/cmake-Debug -DCMAKE_BUILD_TYPE=Debug cmake --build build/cmake-Debug --target subspace_client_rust cd rust_client && cargo build && cargo test -- --test-threads=1 ``` Java/JNI for Android: ```bash bazel build \ //android/jni:libsubspace_jni.so \ //android/java:subspace-java \ //android/java:subspace-java-test \ --config=android_x86_64 cmake -S . -B build/android \ -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=x86_64 \ -DANDROID_PLATFORM=android-28 \ -DANDROID_STL=c++_shared \ -DCMAKE_BUILD_TYPE=Release \ -DPROTOC_EXECUTABLE=/path/to/matching/host/protoc cmake --build build/android --target subspace_jni subspace_java subspace_java_test ``` Soong/Blueprint in AOSP: ```bash m external.subspace-libsubspace_client-soong \ external.subspace-libsubspace_jni-soong \ external.subspace-subspace-java-soong \ external.subspace-subspace_java_client_test-soong ``` ## References - C++ API: `client/client.h` - Python examples: `client/python/client_test.py` - Rust guide: `docs/rust-client.md` - Java API: `android/java/com/subspace/*.java` - Android build/run details: `docs/android.md` - Platform build skill: `.cursor/skills/subspace-builds/SKILL.md`