Skip to content

Commit

Permalink
WIP: Restructure app
Browse files Browse the repository at this point in the history
  • Loading branch information
jsparber committed Jan 29, 2025
1 parent 47f3aea commit b3245c1
Show file tree
Hide file tree
Showing 12 changed files with 469 additions and 203 deletions.
19 changes: 14 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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"]
9 changes: 3 additions & 6 deletions aardvark-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@ 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 = ["full"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
sourceview = { package = "sourceview5", version = "0.9" }
tracing = "0.1"

[dependencies.adw]
package = "libadwaita"
version = "0.7"
features = ["v1_6"]
features = ["v1_6"]
169 changes: 30 additions & 139 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::AardvarkWindow;
use crate::config::VERSION;
use crate::document::Document;
use crate::glib::closure_local;
use crate::{AardvarkTextBuffer, 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());
window.present();
}
}

Expand Down Expand Up @@ -117,7 +96,16 @@ 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) {
println!("New window");
let window = AardvarkWindow::new(self);
window.present();
}

fn show_about(&self) {
Expand All @@ -135,101 +123,4 @@ impl AardvarkApplication {

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");
});
}
}
65 changes: 65 additions & 0 deletions aardvark-app/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::fmt;
use anyhow::Result;
use automerge::transaction::Transactable;
use automerge::{AutoCommit, AutoSerde, ObjId, ObjType, Patch, ReadDoc};
use tokio::sync::{mpsc, oneshot};
use automerge::PatchAction;

/// Hard-coded automerge document schema in bytes representation for "Aardvark".
///
Expand Down Expand Up @@ -38,6 +40,26 @@ impl Document {
Self {
doc: RefCell::new(doc),
}

self.tx.set(tx).unwrap();

glib::spawn_future_local(clone!(#[weak] window, async move {
while let Some(bytes) = rx.recv().await {
window.ingest_message(bytes);
}
}));

buffer.connect_text_changed(|_buffer: AardvarkTextBuffer,
position: i32,
del: i32,
text: &str| {
let bytes = document.save_incremental();
glib::spawn_future_local(async move {
tx.send(bytes)
.await
.expect("sending message to networking backend");
});
});
}

pub fn from_bytes(bytes: &[u8]) -> Self {
Expand Down Expand Up @@ -93,6 +115,49 @@ impl Document {
let mut doc = self.doc.borrow_mut();
doc.save_incremental()
}


fn ingest_message(&self, message: Vec<u8>) {
let document = self.imp().document;
let buffer = self.imp().text_view.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}"
);
self.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() {
self.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());
}
}

impl Default for Document {
Expand Down
1 change: 0 additions & 1 deletion aardvark-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
mod application;
mod config;
mod components;
mod document;
mod textbuffer;
mod window;

Expand Down
Loading

0 comments on commit b3245c1

Please sign in to comment.