From e9acef09678073fffef8cf02975ed37898a56347 Mon Sep 17 00:00:00 2001 From: Andrea Cornell Date: Thu, 21 Sep 2023 16:47:14 -0400 Subject: [PATCH] add notes and rust stuff --- Cargo.lock | 443 +++++++++++++++++++++++++++ Cargo.toml | 2 + notes | 107 +++++++ sharedmodel/Cargo.toml | 20 ++ sharedmodel/src/html_nodes.rs | 16 + sharedmodel/src/lib.rs | 5 + sharedmodel/src/live_event_buffer.rs | 137 +++++++++ sharedmodel/src/main.rs | 205 +++++++++++++ sharedmodel_wasm/Cargo.toml | 15 + sharedmodel_wasm/src/lib.rs | 107 +++++++ 10 files changed, 1057 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 notes create mode 100644 sharedmodel/Cargo.toml create mode 100644 sharedmodel/src/html_nodes.rs create mode 100644 sharedmodel/src/lib.rs create mode 100644 sharedmodel/src/live_event_buffer.rs create mode 100644 sharedmodel/src/main.rs create mode 100644 sharedmodel_wasm/Cargo.toml create mode 100644 sharedmodel_wasm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2bb85bc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,443 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "futf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "getrandom" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "cfg-if 0.1.10", + "libc", +] + +[[package]] +name = "html5ever" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itoa" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae38d669396ca9b707bfc3db254bc382ddb94f57cc5c235f34623a669a01dab" +dependencies = [ + "log", + "phf", + "phf_codegen", + "serde", + "serde_derive", + "serde_json", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cdc457076c78ab54d5e0d6fa7c47981757f1e34dc39ff92787f217dede586c4" +dependencies = [ + "unreachable", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core", +] + +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" + +[[package]] +name = "serde" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" + +[[package]] +name = "serde_derive" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharedmodel" +version = "0.1.0" +dependencies = [ + "html5ever", + "string_cache", + "uuid", +] + +[[package]] +name = "sharedmodel_wasm" +version = "0.1.0" +dependencies = [ + "js-sys", + "sharedmodel", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "siphasher" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83da420ee8d1a89e640d0948c646c1c088758d3a3c538f943bfa97bdac17929d" + +[[package]] +name = "string_cache" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tendril" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de21546595a0873061940d994bbbc5c35f024ae4fd61ec5c5b159115684f508" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + +[[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" + +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "Could not get crate checksum" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..91d4007 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ['sharedmodel', 'sharedmodel_wasm'] diff --git a/notes b/notes new file mode 100644 index 0000000..0750516 --- /dev/null +++ b/notes @@ -0,0 +1,107 @@ +WebRTC + +https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Simple_RTCDataChannel_sample + +WebSocket SaaS +https://github.com/ably/ably-js + +Conflict-free replicated data types + +https://crdt.tech/papers.html +https://arxiv.org/abs/1806.10254 + +Bazel + +https://brentley.dev/patching-bazel-external-dependencies/ +https://bazelbuild.github.io/rules_rust/ + +Rust + +https://salsa.debian.org/rust-team/debcargo/ +https://wiki.debian.org/Teams/RustPackaging/Policy +https://docs.rs/tui/latest/tui/ + +LC in-browser initiative +name "shared model" +Speedcounting relies on strikebot to react fast, which leads to mistake strikes +and requires rate limit circumvention. + +With "shared model" we enable strike bot to react much slower, while giving +human feedback for speedcounting with even lower latency than strike bot can. + +Each in-browser view computes which updates to display as stricken, as +prediction of the eventual thread state. This relies on the following aspects: +* Views are eventually consistent on the set of updates in the viewport, their +order, and which ones are stricken and deleted, without needing to refresh. +At the most basic this will require transpo handling, but it also requires +pulling liveupdate listings, critically when there is a websocket +discontinuity, but at other times also: e.g. to confirm strikes. +* Strike bot broadcasts thread tracking information periodically. The tracking +information has to cover from the strike bot "frozen" timestamp (prior to this +timestamp, the count tracking is reflected in reddit data and strike bot won't +issue new strikes or deletes; can be far in the past and/or many updates ago +if strike bot is overloaded) up until relatively recently (~25 updates ago), +because views will have to load this far back in order to do local thread +tracking. This may not have to include an entry for every update ID, and +probably shouldn't due to traffic volume. + +We can actually do this WITHOUT P2P which is good because otherwise it won't +happen. + +Strike bot HA: two or more instances can coordinate via reddit posts (comments) +to elect a leader; the followers don't strike or delete but remain ready to +promote if the leader disappears. No need to load-balance. Basically each +instance posts "I'm alive" periodically to a separate area (editing a reddit +comment); the leader instance is the lowest-numbered area whose timestamp isn't +too stale. + +Strike bots do have to broadcast with this system so we need to be able to +WebSocket connect to them. Hosting: bingus, strig (while exists), jcornell.net, +occs? + +Ooh or we could use reddit for the broadcasts...hehehe. +Basically we have some fixed amount of space (10000 code points: use base65536) +and we want to encode...essentially a "valid count" list of update IDs. + +Handling count_better resets: +- find the very next update that "can" be a valid count: + - has a number + - not stricken nor deleted + - it could be the same one as contains the reset command. +- assume this one is valid; subsequent updates are (re)interpreted accordingly. +- now find the latest valid count that *precedes* the reset. Try every + subsequent update, look for one that begins a chain of valid counts leading + up to the post-reset count. + +Algorithm for thread interpretation +Start with no last_valid_ix, empty set of restart points, reset flag clear, ix = 0 +Loop start + If ix is out of bounds + If last_valid + Mark everything invalid after last_valid + Break + Failure + If update ix is not stricken + If update ix is reset command + Set reset flag + If update ix has number (n) + if update ix matches last_valid + Matching means last_valid was n-1 and the author is different + Set valid=true + Else if reset flag OR (no last_valid AND this update is blessed in the latest leader message) + Set ix to the earliest matching restart point, if any + Matching means the end of the restart point is n-1 with different author + Set valid=true + Else + If last_valid + Mark ix invalid + Is there a matching restart point? + Update the end of the earliest matching restart point to ix and remove other matching restart points + If no restart points matched + Create a new one with ix as start and end + If valid: + Clear the set of restart points + Clear the reset flag + Mark ix valid + Set last_valid to ix + Increment ix diff --git a/sharedmodel/Cargo.toml b/sharedmodel/Cargo.toml new file mode 100644 index 0000000..5113d4a --- /dev/null +++ b/sharedmodel/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "sharedmodel" +version = "0.1.0" +authors = ["Andrea Cornell "] +edition = "2018" + +#[lib] +#name = "sharedmodel" +#path = "src/lib.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +#[[bin]] +#name = "sharedmodel" +#path = "src/main.rs" + +[dependencies] +uuid = "0.8" +html5ever = "0.25" +string_cache = "0.8" diff --git a/sharedmodel/src/html_nodes.rs b/sharedmodel/src/html_nodes.rs new file mode 100644 index 0000000..a585349 --- /dev/null +++ b/sharedmodel/src/html_nodes.rs @@ -0,0 +1,16 @@ +use std::borrow::Borrow; + +#[derive(Debug)] +pub enum HtmlNode { + Text(T), + Element(U) +} + +pub trait HtmlElement: Sized { + type Text1: AsRef; + type Text2: AsRef; + type ContentsIter: DoubleEndedIterator>; + fn name(&self) -> &str; + fn contents(self) -> Self::ContentsIter; + fn text(&self) -> Self::Text2; +} diff --git a/sharedmodel/src/lib.rs b/sharedmodel/src/lib.rs new file mode 100644 index 0000000..7a10431 --- /dev/null +++ b/sharedmodel/src/lib.rs @@ -0,0 +1,5 @@ +mod live_event_buffer; +mod html_nodes; + +pub use live_event_buffer::LiveUpdateBuffer; +pub use html_nodes::{HtmlNode, HtmlElement}; diff --git a/sharedmodel/src/live_event_buffer.rs b/sharedmodel/src/live_event_buffer.rs new file mode 100644 index 0000000..dc4e36e --- /dev/null +++ b/sharedmodel/src/live_event_buffer.rs @@ -0,0 +1,137 @@ +use uuid::Uuid; +use crate::html_nodes::HtmlElement; + +pub struct LiveUpdateMeta { + id: Uuid, + author: String, + stricken: bool +} + +struct BufferedUpdate { + inner: LiveUpdateMeta, + counted_number: Option, + is_reset: bool, + bad_format: bool, + is_valid: bool +} + +pub struct LiveUpdateBuffer { + capacity: usize, + buf: Vec +} + +pub struct LeaderMessage { + blessed_ids: Vec +} + +pub enum BufferEvent { + Add { before: Option, meta: LiveUpdateMeta, body: T }, + SetValid { id: Uuid, valid: bool }, + Strike { id: Uuid }, + Remove { id: Uuid } +} + +impl LiveUpdateBuffer { + pub fn handle_updates(&mut self, u: &mut Vec<(LiveUpdateMeta, &T)>, ev: &mut Vec>) { + + } + + pub fn handle_strike(&mut self, id: Uuid, ev: &mut Vec>) { + + } + + pub fn handle_delete(&mut self, id: Uuid, ev: &mut Vec>) { + + } + + pub fn handle_leader_msg(&mut self, data: LeaderMessage, ev: &mut Vec>) { + + } + + fn can_follow(&self, ix1: usize, ix2: usize) -> bool { + let upd1 = self.buf.get(ix1).unwrap(); + let upd2 = self.buf.get(ix2).unwrap(); + if upd1.inner.stricken || upd2.inner.stricken || upd1.inner.author.eq(&upd2.inner.author) { + return false; + } + match (upd1.counted_number, upd2.counted_number) { + (Some(n1), Some(n2)) => n2 == n1 + 1, + _ => false + } + } + + fn mark_valid(&mut self, ix: usize, valid: bool, ev: &mut Vec>) { + let upd = self.buf.get_mut(ix).unwrap(); + upd.is_valid = valid; + ev.push(BufferEvent::SetValid { id: upd.inner.id.clone(), valid }) + } + + fn interpret_thread(&mut self, ev: &mut Vec>) { + let mut last_valid: Option = None; + let mut restart_points = Vec::<(usize, usize)>::new(); + let mut reset_pending = false; + let mut ix = 0; + loop { + if ix >= self.buf.len() { + if let Some(last_valid) = last_valid { + for ix in last_valid..ix { + self.mark_valid(ix, false, ev); + } + } // else: failure + break; + } + let upd = self.buf.get(ix).unwrap(); + if !upd.inner.stricken { + if upd.is_reset { + reset_pending = true; + } + if let Some(n) = upd.counted_number { + let is_valid = last_valid.map_or(self.check_blessed(ix), |last_valid| self.can_follow(last_valid, ix)) || reset_pending; + if reset_pending { + if let Some(restart_from) = restart_points.iter().find_map(|(restart_from, restart_to)| self.can_follow(*restart_to, ix).then_some(restart_from) ) { +// if let Some(restart_from) = restart_points.iter().find_map(|(restart_from, restart_to)| /*self.can_follow(*restart_to, ix).then_some(restart_from)*/ if self.can_follow(*restart_to, ix) {Some(restart_from)} else {None}) { + ix = *restart_from; + } + } + if is_valid { + restart_points.clear(); + reset_pending = false; + self.mark_valid(ix, true, ev); + last_valid = Some(ix); + } + else { + if last_valid.is_some() { + self.mark_valid(ix, false, ev); + } + let mut cur = 0; + let mut matching = None; + restart_points.retain(|(restart_from, restart_to)| { + let cur0 = cur; + cur += 1; + if !self.can_follow(*restart_to, ix) { + return true; + } + if matching.is_none() { + matching = Some(cur0); + return true; + } + false + }); + match matching { + Some(ix) => { + let o: &mut (usize, usize) = restart_points.get_mut(ix).unwrap(); + o.1 = ix }, + None => restart_points.push((ix, ix)) + } + } + } + } + ix += 1; + } + } + + fn check_blessed(&self, ix: usize) -> bool { + // Check the latest leader message to see if the given update is blessed + false + } +} diff --git a/sharedmodel/src/main.rs b/sharedmodel/src/main.rs new file mode 100644 index 0000000..37a8bc4 --- /dev/null +++ b/sharedmodel/src/main.rs @@ -0,0 +1,205 @@ +use std::cell::{Cell, RefCell}; +use std::rc::{Rc, Weak}; +use std::borrow::Cow; + +use html5ever::tree_builder::{TreeSink, ElementFlags}; +use html5ever::{QualName, ExpandedName, LocalNameStaticSet, Attribute}; +use html5ever::tendril::{StrTendril, TendrilSink}; +use html5ever::interface::{NodeOrText, QuirksMode}; +use html5ever::driver::ParseOpts; +use string_cache::Atom; + +use sharedmodel::{HtmlNode, HtmlElement}; + +fn main() { + let mut x = html5ever::parse_fragment(RcHtmlElement(Rc::new(HtmlElementInner::root())), ParseOpts::default(), QualName::new(None, Atom::from("http://www.w3.org/1999/xhtml"), Atom::from("body")), Vec::new()); + //let html = "

This is a paragraph. There are

  • nested
  • tags, especially in here
"; + //let html = "

A paragraph

"; + let html = "

Can you handle this

ab
12
34
"; + x.process(StrTendril::from(html)); + let tr = x.finish(); + println!("{:?}", tr); + println!("{}", tr.text()) +} + +type RcNode<'a> = HtmlNode>; + +// #[derive(Debug)] +struct HtmlElementInner<'a> { + name: QualName, + parent: Cell>>, + contents: RefCell>> +} + +impl HtmlElementInner<'_> { + fn root() -> Self { + let name = QualName::new(None, Atom::from(""), Atom::from("")); + let parent = Cell::new(Weak::new()); + let contents = RefCell::new(Vec::new()); + HtmlElementInner { name, parent, contents } + } +} + +//#[derive(Clone, Debug)] +#[derive(Clone)] +struct RcHtmlElement<'a>(Rc>); + +fn nodeortext_to_htmlnode<'a>(v: NodeOrText>) -> RcNode<'a> { + match v { + NodeOrText::AppendNode(h) => HtmlNode::Element(h), + NodeOrText::AppendText(s) => HtmlNode::Text(s) + } +} + +fn node_eq_elem<'a>(node: &RcNode<'a>, elem: &RcHtmlElement<'a>) -> bool { + match node { + HtmlNode::Element(elem1) if Rc::ptr_eq(&elem.0, &elem1.0) => true, + _ => false + } +} + +impl<'a> TreeSink for RcHtmlElement<'a> { + type Handle = Self; + type Output = UniqHtmlElement; + fn finish(self) -> UniqHtmlElement { + // let HtmlElementInner { name, parent, contents } = Rc::try_unwrap(self.0).map_err(|_| ()).expect("no refs left around"); + let HtmlElementInner { ref name, ref contents, .. } = *self.0; + let name = name.local.clone(); + let contents = contents.borrow_mut().drain(..).map(|node| { + match node { + HtmlNode::Text(s) => HtmlNode::Text(s), + HtmlNode::Element(e) => HtmlNode::Element(e.finish()) + } + }).collect(); + UniqHtmlElement { name, contents } + } + + fn parse_error(&mut self, msg: Cow<'static, str>) { + println!("{}", msg); + panic!("how do parse??"); + } + + fn get_document(&mut self) -> Self::Handle { + self.clone() + } + + fn elem_name<'b>(&'b self, target: &'b Self::Handle) -> ExpandedName<'b> { + target.0.name.expanded() + } + + fn create_element(&mut self, name: QualName, attrs: Vec, flags: ElementFlags) -> Self::Handle { + assert_eq!(flags.template, false); + assert_eq!(flags.mathml_annotation_xml_integration_point, false); + let contents = RefCell::new(Vec::new()); + let parent = Cell::new(Weak::new()); + // handily, we never use attribute data. Toss it + RcHtmlElement(Rc::new(HtmlElementInner { name, parent, contents })) + } + + fn create_comment(&mut self, text: StrTendril) -> Self::Handle { + panic!("HTML parser encountered a comment"); + } + + fn create_pi(&mut self, target: StrTendril, data: StrTendril) -> Self::Handle { + panic!("HTML parser encountered a processing instruction"); + } + + fn append(&mut self, parent: &Self::Handle, child: NodeOrText) { + let node = nodeortext_to_htmlnode(child); + if let HtmlNode::Element(ref elem) = node { + elem.0.parent.set(Rc::downgrade(&parent.0)); + } + parent.0.contents.borrow_mut().push(node); + } + + fn append_based_on_parent_node(&mut self, element: &Self::Handle, prev_element: &Self::Handle, child: NodeOrText) { + panic!("Not clear what this is for or if it needs to be implemented"); + } + + fn append_doctype_to_document(&mut self, name: StrTendril, public_id: StrTendril, system_id: StrTendril) { + panic!("This can likely be a no-op, just not sure if it gets called for these fragments"); + } + + fn get_template_contents(&mut self, target: &Self::Handle) -> Self::Handle { + panic!("HTML parser encountered a