Skip to content

Commit

Permalink
Restructure app and use loro text crdt (#41)
Browse files Browse the repository at this point in the history
Introduce a new `doc` crate that exposes GObject API to be used in the
app crate. This integrates also the work @adzialocha did to replace
automerge with loro.

Closes: #37
Closes: #19

Sorry for stealing @adzialocha changes in #33 and #39 hope that's fine.
Let me know if you want me to split the commit even more.

---------

Co-authored-by: adz <x12@adz.garden>
Co-authored-by: adz <adzialocha@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 5, 2025
1 parent fedf478 commit 8be5605
Show file tree
Hide file tree
Showing 17 changed files with 1,503 additions and 532 deletions.
589 changes: 505 additions & 84 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[workspace]
resolver = "2"
members = ["aardvark-app", "aardvark-node"]
members = ["aardvark-app", "aardvark-doc", "aardvark-node"]
7 changes: 2 additions & 5 deletions aardvark-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ version = "0.1.0"
edition = "2021"

[dependencies]
aardvark-doc = { path = "../aardvark-doc" }
aardvark-node = { path = "../aardvark-node" }
anyhow = "1.0.94"
automerge = "0.5.12"
gettext-rs = { version = "0.7", features = ["gettext-system"] }
gtk = { version = "0.9", package = "gtk4", features = ["gnome_47"] }
serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.128"
tokio = { version = "1.42.0", features = ["sync"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
sourceview = { package = "sourceview5", version = "0.9" }
tracing = "0.1"

[dependencies.adw]
package = "libadwaita"
Expand Down
175 changes: 35 additions & 140 deletions aardvark-app/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,57 +18,33 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/

use std::cell::{OnceCell, RefCell};

use aardvark_node::network;
use aardvark_doc::service::Service;
use adw::prelude::*;
use adw::subclass::prelude::*;
use gettextrs::gettext;
use gtk::{gio, glib};
use tokio::sync::{mpsc, oneshot};
use automerge::PatchAction;
use gtk::{gio, glib, glib::Properties};

use crate::config::VERSION;
use crate::document::Document;
use crate::glib::closure_local;
use crate::{AardvarkTextBuffer, AardvarkWindow};
use crate::AardvarkWindow;

mod imp {
use super::*;

#[derive(Debug)]
#[derive(Properties, Default)]
#[properties(wrapper_type = super::AardvarkApplication)]
pub struct AardvarkApplication {
pub window: OnceCell<AardvarkWindow>,
pub document: Document,
pub tx: mpsc::Sender<Vec<u8>>,
pub rx: RefCell<Option<mpsc::Receiver<Vec<u8>>>>,
#[allow(dead_code)]
backend_shutdown: oneshot::Sender<()>,
}

impl AardvarkApplication {
#[property(get)]
pub service: Service,
}

#[glib::object_subclass]
impl ObjectSubclass for AardvarkApplication {
const NAME: &'static str = "AardvarkApplication";
type Type = super::AardvarkApplication;
type ParentType = adw::Application;

fn new() -> Self {
let document = Document::default();
let (backend_shutdown, tx, rx) = network::run().expect("running p2p backend");

AardvarkApplication {
document,
backend_shutdown,
tx,
rx: RefCell::new(Some(rx)),
window: OnceCell::new(),
}
}
}

#[glib::derived_properties]
impl ObjectImpl for AardvarkApplication {
fn constructed(&self) {
self.parent_constructed();
Expand All @@ -79,16 +55,19 @@ mod imp {
}

impl ApplicationImpl for AardvarkApplication {
// We connect to the activate callback to create a window when the application has been
// launched. Additionally, this callback notifies us when the user tries to launch a
// "second instance" of the application. When they try to do that, we'll just present any
// existing window.
fn activate(&self) {
let application = self.obj();
let window = application.get_window();
fn startup(&self) {
self.service.startup();
self.parent_startup();
}

// Ask the window manager/compositor to present the window
window.clone().upcast::<gtk::Window>().present();
fn shutdown(&self) {
self.service.shutdown();
self.parent_shutdown();
}

fn activate(&self) {
let window = AardvarkWindow::new(self.obj().as_ref(), &self.service);
window.present();
}
}

Expand Down Expand Up @@ -117,7 +96,19 @@ impl AardvarkApplication {
let about_action = gio::ActionEntry::builder("about")
.activate(move |app: &Self, _, _| app.show_about())
.build();
self.add_action_entries([quit_action, about_action]);
let new_window_action = gio::ActionEntry::builder("new-window")
.activate(move |app: &Self, _, _| app.new_window())
.build();
self.add_action_entries([quit_action, about_action, new_window_action]);
}

fn new_window(&self) {
// FIXME: it should be possible to reuse the same service for multiple windows but
// currently it's not
let service = Service::new();
service.startup();
let window = AardvarkWindow::new(self, &service);
window.present();
}

fn show_about(&self) {
Expand All @@ -128,108 +119,12 @@ impl AardvarkApplication {
.developer_name("The Aardvark Developers")
.version(VERSION)
.developers(vec!["Tobias"])
// Translators: Replace "translator-credits" with your name/username, and optionally an email or URL.
// FIXME: Translators: Replace "translator-credits" with your name/username, and
// optionally an email or URL.
.translator_credits(&gettext("translator-credits"))
.copyright("© 2024 Tobias")
.build();

about.present(Some(&window));
}

pub fn get_window(&self) -> &AardvarkWindow {
// Get the current window or create one if necessary
self.imp().window.get_or_init(|| {
let window = AardvarkWindow::new(self);

{
let application = self.clone();
let mut rx = self
.imp()
.rx
.take()
.expect("rx should be given at this point");

glib::spawn_future_local(async move {
while let Some(bytes) = rx.recv().await {
application.ingest_message(bytes);
}
});
}

{
let application = self.clone();

window.get_text_buffer().connect_closure(
"text-change",
false,
closure_local!(|_buffer: AardvarkTextBuffer,
position: i32,
del: i32,
text: &str| {
application.update_text(position, del, text);
}),
);
}

window
})
}

fn ingest_message(&self, message: Vec<u8>) {
let document = &self.imp().document;
let window = self.imp().window.get().unwrap();
let buffer = window.get_text_buffer();

// Apply remote changes to our local text CRDT
if let Err(err) = document.load_incremental(&message) {
eprintln!(
"failed applying text change from remote peer to automerge document: {err}"
);
window.add_toast(adw::Toast::new(
"The network provided bad data!"
));
return;
}

// Get latest changes and apply them to our local text buffer
for patch in document.diff_incremental() {
match &patch.action {
PatchAction::SpliceText { index, value, .. } => {
buffer.splice(
*index as i32,
0,
value.make_string().as_str(),
);
}
PatchAction::DeleteSeq { index, length } => {
buffer.splice(*index as i32, *length as i32, "");
}
_ => (),
}
}

// Sanity check that the text buffer and CRDT are in the same state
if buffer.full_text() != document.text() {
window.add_toast(adw::Toast::new("The CRDT and the text view have different states!"));
// if the state diverged, use the CRDT as the source of truth
buffer.set_text(&document.text());
}

dbg!(document.text());
}

fn update_text(&self, position: i32, del: i32, text: &str) {
self.imp()
.document
.update(position, del, text)
.expect("update automerge document after text update");

let bytes = self.imp().document.save_incremental();
let tx = self.imp().tx.clone();
glib::spawn_future_local(async move {
tx.send(bytes)
.await
.expect("sending message to networking backend");
});
}
}
5 changes: 2 additions & 3 deletions aardvark-app/src/components/zoom_level_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
use std::cell::Cell;

use adw::subclass::prelude::*;
use gtk::prelude::*;
use gtk::glib;
use gtk::prelude::*;
use sourceview::*;

mod imp {
Expand Down Expand Up @@ -75,7 +75,6 @@ glib::wrapper! {

impl ZoomLevelSelector {
pub fn new() -> Self {
glib::Object::builder()
.build()
glib::Object::builder().build()
}
}
111 changes: 0 additions & 111 deletions aardvark-app/src/document.rs

This file was deleted.

3 changes: 1 addition & 2 deletions aardvark-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
*/

mod application;
mod config;
mod components;
mod document;
mod config;
mod textbuffer;
mod window;

Expand Down
Loading

0 comments on commit 8be5605

Please sign in to comment.