first cut of the C api

This commit is contained in:
Orion Henry 2020-05-07 07:57:34 -07:00
parent c6b6d59aa1
commit c45351b121
12 changed files with 445 additions and 24 deletions
Cargo.toml
automerge-backend-wasm/src
automerge-backend/src
automerge-c
automerge-cli

View file

@ -2,6 +2,7 @@
members = [
"automerge",
"automerge-c",
"automerge-cli",
"automerge-backend",
"automerge-backend-wasm",

View file

@ -1,6 +1,6 @@
//#![feature(set_stdio)]
use automerge_backend::{ActorID, AutomergeError, Backend, ChangeRequest, Clock, ChangeHash};
use automerge_backend::{ActorID, AutomergeError, Backend, ChangeRequest, ChangeHash};
use js_sys::{Array, Uint8Array};
use serde::de::DeserializeOwned;
use serde::Serialize;
@ -102,17 +102,10 @@ impl State {
#[wasm_bindgen(js_name = getMissingDeps)]
pub fn get_missing_deps(&self) -> Result<JsValue, JsValue> {
let clock = self.backend.get_missing_deps();
rust_to_js(&clock)
let hashes = self.backend.get_missing_deps();
rust_to_js(&hashes)
}
/*
#[wasm_bindgen(js_name = getClock)]
pub fn get_clock(&self) -> Result<JsValue, JsValue> {
rust_to_js(&self.backend.clock)
}
*/
#[wasm_bindgen(js_name = getUndoStack)]
pub fn get_undo_stack(&self) -> Result<JsValue, JsValue> {
rust_to_js(&self.backend.undo_stack)

View file

@ -10,6 +10,7 @@ impl ActorMap {
ActorMap(Vec::new())
}
#[allow(dead_code)]
pub fn index_of(&mut self, actor: &ActorID) -> usize {
if let Some(index) = self.0.iter().position(|a| a == actor) {
return index
@ -18,6 +19,7 @@ impl ActorMap {
self.0.len() - 1
}
#[allow(dead_code)]
pub fn actor_for(&self, index: usize) -> Option<&ActorID> {
self.0.get(index)
}

View file

@ -8,6 +8,12 @@ edition = "2018"
name = "automerge"
crate-type = ["staticlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
automerge-backend = { path = "../automerge-backend" }
libc = "^0.2"
serde = "^1.0"
serde_json = "^1.0"
errno = "^0.2"
[build-dependencies]
cbindgen = "^0.14"

View file

@ -1,17 +1,30 @@
CC=gcc
CFLAGS=-I.
DEPS = automerge.h
LIBS=-lautomerge -lpthread -ldl
LDIR=../target/debug
DEPS=automerge.h
LIBS=-lautomerge -lpthread -ldl -lm
LDIR=../target/release
LIB=../target/release/libautomerge.a
DEBUG_LIB=../target/debug/libautomerge.a
all: automerge $(LIB)
debug: LDIR=../target/debug
debug: automerge $(DEBUG_LIB)
automerge: automerge.o $(LDIR)/libautomerge.a
$(CC) -o $@ automerge.o $(LIBS) -L$(LDIR)
$(DEBUG_LIB): src/lib.rs
cargo build
$(LIB): src/lib.rs
cargo build --release
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
automerge: automerge.o
$(CC) -o $@ $^ $(LIBS) -L$(LDIR)
.PHONY: clean
clean:
rm -f *.o automerge
rm -f *.o automerge $(LIB) $(DEBUG_LIB)

View file

@ -4,6 +4,14 @@
#include "automerge.h"
int main() {
int sum = add(1,2);
printf("1 + 2 = %d\n", sum);
printf("begin\n");
Backend * b = automerge_init();
const char * request = R"({"requestType":"change","actor":"111111","seq":1,"time":0,"version":0,"ops":[{"action":"set","obj":"00000000-0000-0000-0000-000000000000","key":"bird","value":"magpie"}]})";
printf("request: %s\n",request);
const char * patch = automerge_apply_local_change(b, request);
printf("patch: %s\n",patch);
printf("free resources\n");
automerge_free_string(patch);
automerge_free(b);
printf("end\n");
}

View file

@ -1,3 +1,61 @@
#ifndef automerge_h
#define automerge_h
int add(int a, int b);
/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
#include <stdint.h>
#include <stdbool.h>
typedef struct Backend Backend;
typedef struct {
uintptr_t len;
uintptr_t cap;
uint8_t *ptr;
} Change;
typedef struct {
uintptr_t len;
uintptr_t cap;
Change *ptr;
} Changes;
typedef struct {
uintptr_t len;
uintptr_t cap;
uint8_t *ptr;
} Document;
const char *automerge_apply_changes(Backend *backend, uintptr_t num_changes, const Change *changes);
const char *automerge_apply_local_change(Backend *backend, const char *request);
Backend *automerge_clone(Backend *backend);
void automerge_free(Backend *backend);
void automerge_free_change(Change *change);
void automerge_free_changes(Changes *changes);
void automerge_free_document(Document *doc);
void automerge_free_string(const char *string);
const Changes *automerge_get_changes(Backend *backend, uintptr_t len, const uint8_t *binary);
const Changes *automerge_get_changes_for_actor(Backend *backend, const char *actor);
const char *automerge_get_missing_deps(Backend *backend);
const char *automerge_get_patch(Backend *backend);
Backend *automerge_init(void);
Backend *automerge_load(uintptr_t len, const uint8_t *binary);
void automerge_load_changes(Backend *backend, uintptr_t num_changes, const Change *changes);
const Document *automerge_save(Backend *backend);
#endif /* automerge_h */

15
automerge-c/build.rs Normal file
View file

@ -0,0 +1,15 @@
extern crate cbindgen;
use std::env;
use std::path::PathBuf;
fn main() {
let crate_dir = PathBuf::from(
env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR env var is not defined"),
);
let config = cbindgen::Config::from_file("cbindgen.toml")
.expect("Unable to find cbindgen.toml configuration file");
cbindgen::generate_with_config(&crate_dir, config).expect("ohno").write_to_file(crate_dir.join("automerge.h"));
}

View file

@ -0,0 +1,8 @@
include_guard = "automerge_h"
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
language = "C"
includes = []
sys_includes = ["stdint.h", "stdbool.h"]
no_includes = true
line_length = 140

View file

@ -1,7 +1,295 @@
#![feature(vec_into_raw_parts)]
extern crate libc;
extern crate errno;
extern crate serde;
extern crate automerge_backend;
use automerge_backend::{ChangeRequest, Patch, ActorID};
use std::ffi::{CStr, CString};
use std::ops::{Deref, DerefMut};
use std::ptr;
use std::os::raw::{ c_void, c_char };
use serde::ser::Serialize;
use errno::{set_errno,Errno};
fn to_json<T: Serialize>(data: T) -> *const c_char {
let json = serde_json::to_string(&data).unwrap();
let json = CString::new(json).unwrap();
json.into_raw()
}
#[derive(Clone)]
pub struct Backend(automerge_backend::Backend);
impl Deref for Backend {
type Target = automerge_backend::Backend;
fn deref(&self) -> &Self::Target {
&self.0
}
}
unsafe fn from_buf_raw<T>(ptr: *const T, elts: usize) -> Vec<T> {
let mut dst = Vec::with_capacity(elts);
dst.set_len(elts);
ptr::copy(ptr, dst.as_mut_ptr(), elts);
dst
}
impl DerefMut for Backend {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[repr(C)]
pub struct Value {
pub datatype: usize,
pub data: *mut c_void,
}
#[repr(C)]
pub struct Document {
pub len: usize,
pub cap: usize,
pub ptr: *mut u8,
}
#[repr(C)]
pub struct Change {
pub len: usize,
pub cap: usize,
pub ptr: *mut u8,
}
#[repr(C)]
pub struct Changes {
pub len: usize,
pub cap: usize,
pub ptr: *mut Change,
}
impl From<*mut Backend> for Backend {
fn from(b: *mut Backend) -> Self {
unsafe {
*Box::from_raw(b)
}
}
}
impl From<Backend> for *mut Backend {
fn from(b: Backend) -> Self {
Box::into_raw(Box::new(b))
}
}
impl From<Vec<u8>> for Change {
fn from(v: Vec<u8>) -> Self {
let (ptr,len,cap) = v.into_raw_parts();
Change {
ptr, len, cap
}
}
}
impl From<Vec<Change>> for Changes {
fn from(v: Vec<Change>) -> Self {
let (ptr,len,cap) = v.into_raw_parts();
Changes {
ptr, len, cap
}
}
}
impl From<Changes> for *const Changes {
fn from(c: Changes) -> Self {
Box::into_raw(Box::new(c))
}
}
impl From<Vec<u8>> for Document {
fn from(v: Vec<u8>) -> Self {
let (ptr,len,cap) = v.into_raw_parts();
Document {
ptr, len, cap
}
}
}
impl From<Document> for *const Document {
fn from(c: Document) -> Self {
Box::into_raw(Box::new(c))
}
}
/*
init => automerge_init
clone => automerge_clone
free => automerge_free
save => automerge_save
load => automerge_load
applyLocalChange => automerge_apply_local_change
getPatch => automerge_get_patch
applyChanges => automerge_apply_changes
loadChanges => automerge_load_changes
getChangesForActor => automerge_get_changes_for_actor
getChanges => automerge_get_changes
getMissingDeps => automerge_get_missing_deps
getUndoStack => ..
getRedoStack => ..
*/
#[no_mangle]
pub extern "C" fn add(a: i32, b:i32) -> i32 {
a + b
pub extern "C" fn automerge_init() -> *mut Backend {
Backend(automerge_backend::Backend::init()).into()
}
#[no_mangle]
pub unsafe extern "C" fn automerge_free(backend: *mut Backend) {
let backend : Backend = backend.into();
drop(backend)
}
#[no_mangle]
pub unsafe extern "C" fn automerge_free_string(string: *const c_char) {
let string : CString = CString::from_raw(std::mem::transmute(string));
drop(string);
}
#[no_mangle]
pub unsafe extern "C" fn automerge_free_changes(changes: *mut Changes) {
let changes = Box::from_raw(changes);
let changes : Vec<Change> = Vec::from_raw_parts(changes.ptr, changes.len, changes.cap);
let data : Vec<Vec<u8>> = changes.iter().map(|ch| Vec::from_raw_parts(ch.ptr, ch.len, ch.cap)).collect();
drop(data);
drop(changes);
}
#[no_mangle]
pub unsafe extern "C" fn automerge_free_document(doc: *mut Document) {
let doc = Box::from_raw(doc);
let doc : Vec<u8> = Vec::from_raw_parts(doc.ptr, doc.len, doc.cap);
drop(doc)
}
#[no_mangle]
pub unsafe extern "C" fn automerge_free_change(change: *mut Change) {
let change = Box::from_raw(change);
let change : Vec<u8> = Vec::from_raw_parts(change.ptr, change.len, change.cap);
drop(change)
}
#[no_mangle]
pub unsafe extern "C" fn automerge_apply_local_change(backend: *mut Backend, request: *const c_char) -> *const c_char {
let request : &CStr = CStr::from_ptr(request);
let request = request.to_string_lossy();
let request : ChangeRequest = serde_json::from_str(&request).unwrap();
let patch : Patch = (*backend).apply_local_change(request).unwrap();
to_json(patch)
}
#[no_mangle]
pub unsafe extern "C" fn automerge_apply_changes(backend: *mut Backend, num_changes: usize, changes: *const Change) -> *const c_char {
let mut c : Vec<Vec<u8>> = Vec::new();
for i in 0..num_changes {
let change = changes.offset(i as isize);
let bytes = from_buf_raw((*change).ptr, (*change).len);
c.push(bytes)
}
if let Ok(patch) = (*backend).apply_changes_binary(c) {
to_json(patch)
} else {
set_errno(Errno(1));
ptr::null()
}
}
#[no_mangle]
pub unsafe extern "C" fn automerge_get_patch(backend: *mut Backend) -> *const c_char {
if let Ok(patch) = (*backend).get_patch() {
to_json(patch)
} else {
set_errno(Errno(1));
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn automerge_load_changes(backend: *mut Backend, num_changes: usize, changes: *const Change) {
let mut c : Vec<Vec<u8>> = Vec::new();
for i in 0..num_changes {
let change = changes.offset(i as isize);
let bytes = from_buf_raw((*change).ptr, (*change).len);
c.push(bytes)
}
if let Err(_) = (*backend).load_changes_binary(c) {
set_errno(Errno(1));
}
}
#[no_mangle]
pub unsafe extern "C" fn automerge_clone(backend: *mut Backend) -> *mut Backend {
(*backend).clone().into()
}
#[no_mangle]
pub unsafe extern "C" fn automerge_save(backend: *mut Backend) -> *const Document {
let backend : Backend = backend.into();
if let Ok(data) = backend.save() {
let change : Document = data.into();
change.into()
} else {
set_errno(Errno(1));
ptr::null()
}
}
#[no_mangle]
pub unsafe extern "C" fn automerge_load(len: usize, binary: *const u8) -> *mut Backend {
let bytes = from_buf_raw(binary, len);
if let Ok(backend) = automerge_backend::Backend::load(bytes) {
Backend(backend).into()
} else {
set_errno(Errno(1));
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn automerge_get_changes_for_actor(backend: *mut Backend, actor: *const c_char) -> *const Changes {
let actor : &CStr = CStr::from_ptr(actor);
let actor = actor.to_string_lossy();
let actor : ActorID = actor.as_ref().into();
if let Ok(mut changes) = (*backend).get_changes_for_actor_id(&actor) {
let changes : Vec<Change> = changes.drain(..).map(|c| c.into()).collect();
let changes : Changes = changes.into();
changes.into()
} else {
set_errno(Errno(1));
ptr::null()
}
}
#[no_mangle]
pub unsafe extern "C" fn automerge_get_changes(backend: *mut Backend, len: usize, binary: *const u8) -> *const Changes {
let mut have_deps = Vec::new();
for i in 0..len {
have_deps.push(from_buf_raw(binary.offset(i as isize * 32),32).as_slice().into())
}
if let Ok(mut changes) = (*backend).get_changes(&have_deps) {
let changes : Vec<Change> = changes.drain(..).map(|c| c.into()).collect();
let changes : Changes = changes.into();
changes.into()
} else {
set_errno(Errno(1));
ptr::null()
}
}
#[no_mangle]
pub unsafe extern "C" fn automerge_get_missing_deps(backend: *mut Backend) -> *const c_char {
let missing = (*backend).get_missing_deps();
to_json(missing)
}
#[cfg(test)]

11
automerge-cli/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "automerge-cli"
version = "0.1.0"
authors = ["Orion Henry <orion.henry@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
automerge-backend = { path = "../automerge-backend" }

18
automerge-cli/src/main.rs Normal file
View file

@ -0,0 +1,18 @@
extern crate automerge_backend;
use automerge_backend::{Backend};
use std::fs::File;
use std::io::prelude::*;
fn main() -> std::io::Result<()> {
let args : Vec<_> = std::env::args().collect();
if args.len() != 2 {
println!("usage: automerge PATH");
return Ok(());
}
let data : Vec<u8> = std::fs::read(&args[1])?;
println!("DATA: {:?}",data.len());
let mut db = Backend::init();
db.load_changes_binary(vec![data]).unwrap();
return Ok(());
}