use crate::antimatter::json_crdt::{JsonCrdt, JsonPatch};
use crate::antimatter::messages::{Message, Patch};
use crate::antimatter::AntimatterCrdt;
use crate::core::client::BraidClient;
use crate::core::traits::NativeRuntime;
use crate::core::types::{BraidRequest, Update};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_void};
use std::sync::Arc;
pub struct BraidAntimatter {
inner: AntimatterCrdt<JsonCrdt>,
}
pub type BraidCallback = extern "C" fn(user_data: *mut c_void, json_ptr: *const c_char);
struct FfiCallbackContext {
callback: BraidCallback,
user_data: *mut c_void,
}
unsafe impl Send for FfiCallbackContext {}
unsafe impl Sync for FfiCallbackContext {}
#[no_mangle]
pub extern "C" fn braid_antimatter_new(
id: *const c_char,
initial_text: *const c_char,
callback: BraidCallback,
user_data: *mut c_void,
) -> *mut BraidAntimatter {
let id_str = if id.is_null() {
None
} else {
unsafe { CStr::from_ptr(id).to_str().ok().map(|s| s.to_string()) }
};
let text_str = if initial_text.is_null() {
""
} else {
unsafe { CStr::from_ptr(initial_text).to_str().unwrap_or("") }
};
let ctx = Arc::new(FfiCallbackContext {
callback,
user_data,
});
let send_cb = move |msg: Message| {
if let Ok(json) = serde_json::to_string(&msg) {
if let Ok(c_str) = CString::new(json) {
(ctx.callback)(ctx.user_data, c_str.as_ptr());
}
}
};
let runtime = Arc::new(NativeRuntime);
let crdt = JsonCrdt::with_content(&id_str.clone().unwrap_or_default(), text_str);
let inner = AntimatterCrdt::new(id_str, crdt, Arc::new(send_cb), runtime);
Box::into_raw(Box::new(BraidAntimatter { inner }))
}
#[no_mangle]
pub extern "C" fn braid_antimatter_free(instance: *mut BraidAntimatter) {
if !instance.is_null() {
unsafe {
let _ = Box::from_raw(instance);
}
}
}
#[no_mangle]
pub extern "C" fn braid_antimatter_receive(
instance: *mut BraidAntimatter,
json_msg: *const c_char,
) -> *mut c_char {
if instance.is_null() || json_msg.is_null() {
return std::ptr::null_mut();
}
let inst = unsafe { &mut *instance };
let msg_str = unsafe { CStr::from_ptr(json_msg).to_str().unwrap_or("") };
let msg: Message = match serde_json::from_str(msg_str) {
Ok(m) => m,
Err(_) => return std::ptr::null_mut(),
};
let result = match inst.inner.receive(msg) {
Ok(patches) => patches,
Err(_) => return std::ptr::null_mut(),
};
let json_res = serde_json::to_string(&result).unwrap_or_else(|_| "[]".to_string());
CString::new(json_res)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn braid_antimatter_update(
instance: *mut BraidAntimatter,
json_patches: *const c_char,
) -> *mut c_char {
if instance.is_null() || json_patches.is_null() {
return std::ptr::null_mut();
}
let inst = unsafe { &mut *instance };
let patches_str = unsafe { CStr::from_ptr(json_patches).to_str().unwrap_or("") };
let patches: Vec<Patch> = match serde_json::from_str(patches_str) {
Ok(p) => p,
Err(_) => return std::ptr::null_mut(),
};
let version = inst.inner.update(patches);
CString::new(version)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn braid_antimatter_get_content(instance: *mut BraidAntimatter) -> *mut c_char {
if instance.is_null() {
return std::ptr::null_mut();
}
let inst = unsafe { &*instance };
let content = inst.inner.crdt.get_content();
CString::new(content)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn braid_antimatter_subscribe(instance: *mut BraidAntimatter, conn: *const c_char) {
if instance.is_null() || conn.is_null() {
return;
}
let inst = unsafe { &mut *instance };
if let Ok(conn_str) = unsafe { CStr::from_ptr(conn).to_str() } {
inst.inner.subscribe(conn_str.to_string());
}
}
#[no_mangle]
pub extern "C" fn braid_antimatter_disconnect(
instance: *mut BraidAntimatter,
conn: *const c_char,
create_fissure: bool,
) {
if instance.is_null() || conn.is_null() {
return;
}
let inst = unsafe { &mut *instance };
if let Ok(conn_str) = unsafe { CStr::from_ptr(conn).to_str() } {
inst.inner.disconnect(conn_str.to_string(), create_fissure);
}
}
#[no_mangle]
pub extern "C" fn braid_antimatter_ackme(instance: *mut BraidAntimatter) -> *mut c_char {
if instance.is_null() {
return std::ptr::null_mut();
}
let inst = unsafe { &mut *instance };
let ackme_id = inst.inner.ackme();
CString::new(ackme_id)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn braid_string_free(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
let _ = CString::from_raw(ptr);
}
}
}
pub struct BraidClientFfi {
inner: BraidClient,
}
#[no_mangle]
pub extern "C" fn braid_client_new() -> *mut BraidClientFfi {
match BraidClient::new() {
Ok(inner) => Box::into_raw(Box::new(BraidClientFfi { inner })),
Err(_) => std::ptr::null_mut(),
}
}
#[no_mangle]
pub extern "C" fn braid_client_free(client: *mut BraidClientFfi) {
if !client.is_null() {
unsafe {
let _ = Box::from_raw(client);
}
}
}
pub struct BraidSubscriptionFfi {
cancel_tx: tokio::sync::oneshot::Sender<()>,
}
#[no_mangle]
pub extern "C" fn braid_client_subscribe(
client: *mut BraidClientFfi,
url: *const c_char,
callback: BraidCallback,
user_data: *mut c_void,
) -> *mut BraidSubscriptionFfi {
if client.is_null() || url.is_null() {
return std::ptr::null_mut();
}
let cli = unsafe { &*client };
let url_str = unsafe { CStr::from_ptr(url).to_str().unwrap_or("").to_string() };
let (cancel_tx, mut cancel_rx) = tokio::sync::oneshot::channel::<()>();
let ctx = Arc::new(FfiCallbackContext {
callback,
user_data,
});
let inner_client = cli.inner.clone();
tokio::spawn(async move {
let mut sub = match inner_client
.subscribe(&url_str, BraidRequest::new().subscribe())
.await
{
Ok(s) => s,
Err(_) => return,
};
loop {
tokio::select! {
result = sub.next() => {
match result {
Some(res) => {
if let Ok(update) = res {
if let Ok(json) = serde_json::to_string(&update) {
if let Ok(c_str) = CString::new(json) {
(ctx.callback)(ctx.user_data, c_str.as_ptr());
}
}
}
}
None => break,
}
}
_ = &mut cancel_rx => break,
}
}
});
Box::into_raw(Box::new(BraidSubscriptionFfi { cancel_tx }))
}
#[no_mangle]
pub extern "C" fn braid_subscription_free(sub: *mut BraidSubscriptionFfi) {
if !sub.is_null() {
unsafe {
let s = Box::from_raw(sub);
let _ = s.cancel_tx.send(());
}
}
}