Compare commits

...

12 Commits

Author SHA1 Message Date
Green Sky
98af0c288f update imgui 2025-10-07 21:57:35 +02:00
Green Sky
f41bfeaa65 rename fetch ops to fetch add
add delnum to frontier and fetch del
2025-09-08 10:41:03 +02:00
Green Sky
31f5adfcc0 send and apply ops, basic editing working, if no ops are missed 2025-09-02 12:53:26 +02:00
Green Sky
95f3c821a5 remove imgui debug log 2025-09-02 12:50:52 +02:00
Green Sky
9cab041af4 add doc write lock and make imgui use it 2025-09-02 11:36:05 +02:00
Green Sky
396a03c229 better fallback tests and error messages 2025-09-02 11:15:21 +02:00
Green Sky
66b5fdecfb give imgui code access to notes 2025-09-02 11:12:48 +02:00
Green Sky
b6a68c830e misc stuff 2025-09-02 10:57:34 +02:00
Green Sky
3bf46dcaba update imgui 2025-07-15 12:12:49 +02:00
Green Sky
e7096ee20f make contact store version visible 2025-03-10 20:56:53 +01:00
Green Sky
c731d51a46 port to contact4, minor code changes 2025-03-10 16:31:28 +01:00
Erik Scholz
bb45379199 Create LICENSE 2025-02-07 12:28:04 +01:00
14 changed files with 487 additions and 112 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Erik Scholz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -13,7 +13,7 @@ endif()
if (NOT TARGET imgui)
FetchContent_Declare(imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG 5c1d2d1e4c562a2ed3efbc64476e703a655b45fd # v1.91.7
GIT_TAG bf75bfec48fc00f532af8926130b70c0e26eb099 # v1.92.3
EXCLUDE_FROM_ALL
CONFIGURE_COMMAND "" # remove?
)

View File

@@ -1,5 +1,7 @@
#include <solanaceae/plugin/solana_plugin_v1.h>
#include <solanaceae/contact/contact_store_i.hpp>
#include <solanaceae/crdtnotes/crdtnotes.hpp>
#include <solanaceae/crdtnotes/crdtnotes_sync.hpp>
@@ -32,14 +34,15 @@ SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api)
}
try {
auto* cr = PLUG_RESOLVE_INSTANCE_VERSIONED(Contact3Registry, "1");
auto* cs = PLUG_RESOLVE_INSTANCE(ContactStore4I);
// static store, could be anywhere tho
// construct with fetched dependencies
g_crdtn = std::make_unique<CRDTNotes>();
g_crdtns = std::make_unique<CRDTNotesSync>(*g_crdtn, *cr);
g_crdtns = std::make_unique<CRDTNotesSync>(*g_crdtn, *cs);
// register types
PLUG_PROVIDE_INSTANCE(CRDTNotes, plugin_name, g_crdtn.get());
PLUG_PROVIDE_INSTANCE(CRDTNotesSync, plugin_name, g_crdtns.get());
PLUG_PROVIDE_INSTANCE(CRDTNotesEventI, plugin_name, g_crdtns.get());
} catch (const ResolveException& e) {

View File

@@ -1,6 +1,9 @@
#include <solanaceae/plugin/solana_plugin_v1.h>
#include <solanaceae/contact/contact_store_i.hpp>
#include <solanaceae/crdtnotes_imgui/crdtnotes_imgui.hpp>
#include <imgui.h>
#include <entt/entt.hpp>
@@ -32,8 +35,9 @@ SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api)
}
try {
auto* crdtn = PLUG_RESOLVE_INSTANCE(CRDTNotes);
auto* crdtns = PLUG_RESOLVE_INSTANCE(CRDTNotesSync);
auto* cr = PLUG_RESOLVE_INSTANCE_VERSIONED(Contact3Registry, "1");
auto* cs = PLUG_RESOLVE_INSTANCE(ContactStore4I);
auto* imguic = PLUG_RESOLVE_INSTANCE_VERSIONED(ImGuiContext, ImGui::GetVersion());
auto* imguimemaf = PLUG_RESOLVE_INSTANCE_VERSIONED(ImGuiMemAllocFunc, ImGui::GetVersion());
auto* imguimemff = PLUG_RESOLVE_INSTANCE_VERSIONED(ImGuiMemFreeFunc, ImGui::GetVersion());
@@ -45,7 +49,7 @@ SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api)
// static store, could be anywhere tho
// construct with fetched dependencies
g_crdtn_imgui = std::make_unique<CRDTNotesImGui>(*crdtns, *cr);
g_crdtn_imgui = std::make_unique<CRDTNotesImGui>(*crdtn, *crdtns, *cs);
// register types
PLUG_PROVIDE_INSTANCE(CRDTNotesImGui, plugin_name, g_crdtn_imgui.get());

View File

@@ -1,9 +1,10 @@
#include <solanaceae/plugin/solana_plugin_v1.h>
#include <solanaceae/contact/contact_store_i.hpp>
#include <solanaceae/crdtnotes/crdtnotes.hpp>
#include <solanaceae/crdtnotes/crdtnotes_sync.hpp>
#include <solanaceae/crdtnotes_toxsync/crdtnotes_toxsync.hpp>
#include <solanaceae/contact/contact_model3.hpp>
#include <solanaceae/toxcore/tox_interface.hpp>
#include <solanaceae/toxcore/tox_event_interface.hpp>
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
@@ -37,14 +38,14 @@ SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api)
try {
auto* notes_sync = PLUG_RESOLVE_INSTANCE(CRDTNotesEventI);
auto* cr = PLUG_RESOLVE_INSTANCE_VERSIONED(Contact3Registry, "1");
auto* cs = PLUG_RESOLVE_INSTANCE(ContactStore4I);
auto* t = PLUG_RESOLVE_INSTANCE(ToxI);
auto* tep = PLUG_RESOLVE_INSTANCE(ToxEventProviderI);
auto* tcm = PLUG_RESOLVE_INSTANCE(ToxContactModel2);
// static store, could be anywhere tho
// construct with fetched dependencies
g_crdtn_ts = std::make_unique<CRDTNotesToxSync>(*notes_sync, *cr, *t, *tep, *tcm);
g_crdtn_ts = std::make_unique<CRDTNotesToxSync>(*notes_sync, *cs, *t, *tep, *tcm);
// register types
PLUG_PROVIDE_INSTANCE(CRDTNotesToxSync, plugin_name, g_crdtn_ts.get());

View File

@@ -37,3 +37,20 @@ CRDTNotes::Doc* CRDTNotes::addDoc(const CRDTAgent& self_agent, const DocID& id)
return &doc;
}
void CRDTNotes::writeLockRelease(const DocID& id) {
assert(_doc_write_locks.count(id) > 0);
_doc_write_locks.erase(id);
}
bool CRDTNotes::isWriteLocked(const DocID& id) const {
return _doc_write_locks.count(id);
}
std::optional<CRDTNotes::DocWriteLock> CRDTNotes::writeLockAquire(const DocID& id) {
if (_doc_write_locks.count(id)) {
return std::nullopt; // replace with exception instead?
}
_doc_write_locks.emplace(id);
return DocWriteLock{*this, id};
}

View File

@@ -6,6 +6,8 @@
#include <cstdint>
#include <functional>
#include <unordered_map>
#include <unordered_set>
#include <optional>
using ID32 = std::array<uint8_t, 32>;
@@ -35,11 +37,26 @@ class CRDTNotes {
struct Frontier { // newest known seq for given agent
CRDTAgent agent;
uint64_t seq{0};
uint64_t del_num{0};
};
// RAII lock wrapper
struct DocWriteLock {
CRDTNotes* notes;
DocID id;
// ctr assumes lock
DocWriteLock(CRDTNotes& notes, const DocID& id) : notes(&notes), id(id) {}
DocWriteLock(const DocWriteLock&) = delete;
DocWriteLock(DocWriteLock&& other) : notes(other.notes), id(other.id) { other.notes = nullptr; }
~DocWriteLock(void) { if (notes) { notes->writeLockRelease(id); } }
bool operator==(const DocWriteLock& other) const { return id == other.id; }
};
private:
// TODO: add metadata to docs
std::unordered_map<DocID, Doc> _docs;
std::unordered_set<DocID> _doc_write_locks;
public:
// config?
@@ -52,5 +69,16 @@ class CRDTNotes {
Doc* getDoc(const DocID& id);
Doc* addDoc(const CRDTAgent& self_agent, const DocID& doc);
void writeLockRelease(const DocID& id);
bool isWriteLocked(const DocID& id) const;
std::optional<DocWriteLock> writeLockAquire(const DocID& id);
};
template<>
struct std::hash<CRDTNotes::DocWriteLock> {
std::uint64_t operator()(const CRDTNotes::DocWriteLock& s) const noexcept {
return std::hash<ID32>{}(s.id);
}
};

View File

@@ -2,7 +2,7 @@
#include "./crdtnotes.hpp"
#include <solanaceae/contact/contact_model3.hpp>
#include <solanaceae/contact/fwd.hpp>
// send api
struct CRDTNotesContactSyncModelI {
@@ -12,36 +12,42 @@ struct CRDTNotesContactSyncModelI {
public:
// notify of doc existing
virtual void SendGossip(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id
) = 0;
virtual void SendGossip(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const std::vector<CRDTNotes::Frontier>& selected_frontier
const std::vector<CRDTNotes::Frontier>& frontier
) = 0;
// fetch
public:
// causes the other peer to send gossip with all known frontiers (on cool down)
virtual void SendFetchCompleteFrontier(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id
) = 0;
// action range request
virtual void SendFetchOps(
Contact3Handle c,
virtual void SendFetchAddRange(
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const CRDTNotes::CRDTAgent& agent,
const uint64_t seq_from,
const uint64_t seq_to
) = 0;
virtual void SendFetchDel(
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const CRDTNotes::CRDTAgent& agent
) = 0;
public: // ops response
virtual void SendOps(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
// TODO: optimize this
const std::vector<CRDTNotes::Doc::Op>&

View File

@@ -1,13 +1,27 @@
#include "./crdtnotes_sync.hpp"
#include <solanaceae/contact/contact_store_i.hpp>
#include <solanaceae/crdtnotes/crdtnotes_contact_sync_model.hpp>
#include <solanaceae/contact/components.hpp>
#include <entt/container/dense_set.hpp>
#include <cstdint>
#include <vector>
#include <iostream>
namespace Components {
// attached to contact
struct OpSendQueue {
std::map<CRDTNotes::DocID, std::vector<CRDTNotes::Doc::Op>> ops;
// HACK: limit to 5 ops per packet for now
// TODO: ft based alternative for >5 ops
};
} // Components
static ID32 id_from_vec(const std::vector<uint8_t>& vec) {
ID32 new_id;
for (size_t i = 0; i < new_id.size() && i < vec.size(); i++) {
@@ -17,7 +31,7 @@ static ID32 id_from_vec(const std::vector<uint8_t>& vec) {
return new_id;
}
CRDTNotesSync::CRDTNotesSync(CRDTNotes& notes, Contact3Registry& cr) : _notes(notes), _cr(cr) {
CRDTNotesSync::CRDTNotesSync(CRDTNotes& notes, ContactStore4I& cs) : _notes(notes), _cs(cs) {
_rng.seed(std::random_device{}());
_rng.discard(707);
}
@@ -26,6 +40,25 @@ CRDTNotesSync::~CRDTNotesSync(void) {
}
float CRDTNotesSync::iterate(float time_delta) {
for (auto doc_it = _docs_incoming_ops.begin(); doc_it != _docs_incoming_ops.end();) {
if (_notes.isWriteLocked(doc_it->first)) {
doc_it++;
continue;
}
auto lock_opt = _notes.writeLockAquire(doc_it->first);
assert(lock_opt);
auto* doc_ptr = getDoc(doc_it->first);
// TODO: record every applied op and throw event, so eg gui can react better
// , or better yet, edit events in string space (imgui can consume them)
doc_ptr->apply(doc_it->second);
std::cout << "CRDTNotesSync: applied " << doc_it->second.size() << " ops\n";
doc_it = _docs_incoming_ops.erase(doc_it);
}
if (!_gossip_queue.empty()) {
// TODO: set is sorted by id, not by order added
// only one per iterate *should* be enough
@@ -37,7 +70,11 @@ float CRDTNotesSync::iterate(float time_delta) {
// TODO: this is a fallback, remove
if (c.all_of<Contact::Components::ParentOf>()) {
for (const auto child : c.get<Contact::Components::ParentOf>().subs) {
if (c.registry()->all_of<Contact::Components::TagSelfStrong>(child)) {
continue;
}
if (!c.registry()->all_of<CRDTNotesContactSyncModelI*>(child)) {
std::cerr << "CRDTNotesSync: error, fallback failed\n";
continue;
}
@@ -64,7 +101,11 @@ float CRDTNotesSync::iterate(float time_delta) {
// TODO: this is a fallback, remove
if (c.all_of<Contact::Components::ParentOf>()) {
for (const auto child : c.get<Contact::Components::ParentOf>().subs) {
if (c.registry()->all_of<Contact::Components::TagSelfStrong>(child)) {
continue;
}
if (!c.registry()->all_of<CRDTNotesContactSyncModelI*>(child)) {
std::cerr << "CRDTNotesSync: error, fallback failed\n";
continue;
}
@@ -80,22 +121,57 @@ float CRDTNotesSync::iterate(float time_delta) {
_fetch_frontier_queue.erase(it);
}
return 1.f;
bool sending_ops {false};
{ // send ops in queue
std::vector<Contact4> empty_queue;
for (const auto& [c, op_comp, sync_model] : _cs.registry().view<Components::OpSendQueue, CRDTNotesContactSyncModelI*>().each()) {
// HACK: one pkg with up to 5 ops per tick per peer
//for (const auto& [doc_id, op_vec] : op_comp.ops) {
for (auto it = op_comp.ops.begin(); it != op_comp.ops.end();) {
if (it->second.empty()) {
it = op_comp.ops.erase(it);
continue;
} else if (it->second.size() <= 5) {
std::cout << "sending " << it->second.size() << " ops\n";
sync_model->SendOps(_cs.contactHandle(c), it->first, it->second);
it = op_comp.ops.erase(it);
//sending_ops = true;
} else {
std::vector<CRDTNotes::Doc::Op> tmp_ops {it->second.cbegin(), it->second.cbegin()+5};
assert(tmp_ops.size() == 5);
sync_model->SendOps(_cs.contactHandle(c), it->first, tmp_ops);
it->second.erase(it->second.cbegin(), it->second.cbegin()+5);
sending_ops = true;
}
break; // single update only
}
if (op_comp.ops.empty()) {
empty_queue.push_back(c);
}
}
_cs.registry().remove<Components::OpSendQueue>(empty_queue.cbegin(), empty_queue.cend());
}
return sending_ops ? 0.05f : 2.f;
}
CRDTNotes::Doc* CRDTNotesSync::getDoc(const CRDTNotes::DocID& doc_id) {
return _notes.getDoc(doc_id);
}
std::optional<CRDTNotes::DocID> CRDTNotesSync::addNewDoc(Contact3Handle c, bool secret) {
std::optional<CRDTNotes::DocID> CRDTNotesSync::addNewDoc(ContactHandle4 c, bool secret) {
if (!static_cast<bool>(c)) {
std::cerr << "CRDTNS error: invalid contact\n";
return std::nullopt;
}
const auto& self = c.get<Contact::Components::Self>().self;
assert(_cr.all_of<Contact::Components::ID>(self));
const auto& self_id = _cr.get<Contact::Components::ID>(self);
const auto& cr = _cs.registry();
const auto self = c.get<Contact::Components::Self>().self;
assert(cr.all_of<Contact::Components::ID>(self));
const auto& self_id = cr.get<Contact::Components::ID>(self);
assert(!self_id.data.empty());
CRDTNotes::CRDTAgent self_agent_id = id_from_vec(self_id.data);
@@ -127,15 +203,17 @@ std::optional<CRDTNotes::DocID> CRDTNotesSync::addNewDoc(Contact3Handle c, bool
return new_id;
}
bool CRDTNotesSync::addDoc(const CRDTNotes::DocID& doc_id, Contact3Handle c) {
bool CRDTNotesSync::addDoc(const CRDTNotes::DocID& doc_id, ContactHandle4 c) {
if (!static_cast<bool>(c)) {
std::cerr << "CRDTNS error: invalid contact\n";
return false;
}
const auto& cr = _cs.registry();
const auto& self = c.get<Contact::Components::Self>().self;
assert(_cr.all_of<Contact::Components::ID>(self));
const auto& self_id = _cr.get<Contact::Components::ID>(self);
assert(cr.all_of<Contact::Components::ID>(self));
const auto& self_id = cr.get<Contact::Components::ID>(self);
assert(!self_id.data.empty());
CRDTNotes::CRDTAgent self_agent_id = id_from_vec(self_id.data);
@@ -158,12 +236,12 @@ std::vector<CRDTNotes::DocID> CRDTNotesSync::getDocList(void) {
return _notes.getDocList();
}
std::vector<CRDTNotes::DocID> CRDTNotesSync::getDocList(Contact3Handle c) {
std::vector<CRDTNotes::DocID> CRDTNotesSync::getDocList(ContactHandle4 c) {
std::vector<CRDTNotes::DocID> list;
Contact3Handle parent;
ContactHandle4 parent;
if (c.all_of<Contact::Components::Parent>()) {
parent = Contact3Handle{*c.registry(), c.get<Contact::Components::Parent>().parent};
parent = ContactHandle4{*c.registry(), c.get<Contact::Components::Parent>().parent};
}
for (const auto& [k, v] : _docs_contacts) {
@@ -190,7 +268,50 @@ void CRDTNotesSync::merge(const CRDTNotes::DocID& doc_id, std::string_view new_t
auto op_vec = doc_ptr->merge(new_text);
std::cout << "doc changed " << op_vec.size() << " ops generated\n";
// USE OPS
// attach OpSendQueue to every contact
// needs to be placed at the contact with the sync model
entt::dense_set<Contact4> handled_contacts;
for (auto c : _docs_contacts.at(doc_id)) {
if (handled_contacts.contains(c)) {
continue;
}
if (!c.all_of<CRDTNotesContactSyncModelI*>()) {
// TODO: this is a fallback, remove
if (c.all_of<Contact::Components::ParentOf>()) {
for (const auto child : c.get<Contact::Components::ParentOf>().subs) {
if (handled_contacts.contains(child)) {
continue;
}
if (c.registry()->all_of<Contact::Components::TagSelfStrong>(child)) {
continue;
}
if (!c.registry()->all_of<CRDTNotesContactSyncModelI*>(child)) {
std::cerr << "CRDTNotesSync error: fallback failed\n";
continue;
}
auto& op_queue = c.registry()->get_or_emplace<Components::OpSendQueue>(child).ops[doc_id];
if (op_queue.empty()) {
op_queue = op_vec;
} else {
op_queue.insert(op_queue.cend(), op_vec.cbegin(), op_vec.cend());
}
handled_contacts.emplace(child);
}
}
continue; // skip, not impl
}
auto& op_queue = c.get_or_emplace<Components::OpSendQueue>().ops[doc_id];
if (op_queue.empty()) {
op_queue = op_vec;
} else {
op_queue.insert(op_queue.cend(), op_vec.cbegin(), op_vec.cend());
}
handled_contacts.emplace(c);
}
}
void CRDTNotesSync::onCRDTNSyncEvent(Events::NGCEXT_crdtns_gossip&& e) {
@@ -203,9 +324,38 @@ void CRDTNotesSync::onCRDTNSyncEvent(Events::NGCEXT_crdtns_gossip_frontier&& e)
void CRDTNotesSync::onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_complete_frontier&& e) {
}
void CRDTNotesSync::onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_op_range&& e) {
void CRDTNotesSync::onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_add_range&& e) {
}
void CRDTNotesSync::onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_del&& e) {
}
void CRDTNotesSync::onCRDTNSyncEvent(Events::NGCEXT_crdtns_ops&& e) {
addDoc(e.doc_id, e.c);
if (e.ops.empty()) {
std::cerr << "CRDTNotesSync warning: got empty ops event/pkg\n";
return;
}
// TODO: deduplicate ops ?
auto lock_opt = _notes.writeLockAquire(e.doc_id);
if (lock_opt) {
// TODO: perms n stuff
// TODO: check if seq missing
auto* doc_ptr = getDoc(e.doc_id);
// TODO: record every applied op and throw event, so eg gui can react better
// , or better yet, edit events in string space (imgui can consume them)
doc_ptr->apply(e.ops);
// TODO: check if new frontier
} else {
auto& op_in_vec = _docs_incoming_ops[e.doc_id];
if (op_in_vec.empty()) {
op_in_vec = e.ops;
} else {
op_in_vec.insert(op_in_vec.cend(), e.ops.cbegin(), e.ops.cend());
}
}
}

View File

@@ -2,10 +2,15 @@
#include "./crdtnotes.hpp"
#include <solanaceae/contact/contact_model3.hpp>
#include <solanaceae/contact/fwd.hpp>
#include <entt/entity/registry.hpp>
#include <entt/entity/handle.hpp>
#include <set>
#include <random>
#include <unordered_map>
#include <map>
// fwd
struct CRDTNotesContactSyncModelI;
@@ -14,7 +19,7 @@ namespace Events {
// - DocID
struct NGCEXT_crdtns_gossip {
Contact3Handle c;
ContactHandle4 c;
CRDTNotes::DocID doc_id;
};
@@ -22,16 +27,17 @@ namespace Events {
// - array [
// - AgentID
// - seq (frontier)
// - del_num
// - ]
struct NGCEXT_crdtns_gossip_frontier {
Contact3Handle c;
ContactHandle4 c;
CRDTNotes::DocID doc_id;
std::vector<CRDTNotes::Frontier> selected_frontier;
std::vector<CRDTNotes::Frontier> frontier;
};
// - DocID
struct NGCEXT_crdtns_fetch_complete_frontier {
Contact3Handle c;
ContactHandle4 c;
CRDTNotes::DocID doc_id;
};
@@ -39,24 +45,37 @@ namespace Events {
// - AgentID
// - seq_from
// - seq_to
struct NGCEXT_crdtns_fetch_op_range {
Contact3Handle c;
struct NGCEXT_crdtns_fetch_add_range {
ContactHandle4 c;
CRDTNotes::DocID doc_id;
CRDTNotes::CRDTAgent agent;
uint64_t seq_from;
uint64_t seq_to;
};
// - DocID
// - AgentID
struct NGCEXT_crdtns_fetch_del {
ContactHandle4 c;
CRDTNotes::DocID doc_id;
CRDTNotes::CRDTAgent agent;
};
// - DocID
// - array [
// - op
// - ]
struct NGCEXT_crdtns_ops {
Contact3Handle c;
ContactHandle4 c;
CRDTNotes::DocID doc_id;
std::vector<CRDTNotes::Doc::Op> ops;
};
// TODO: curser
// - DocID
// - AgentID
// - CRDTNotes::Doc::ListType::ListID parent_left
} // Events
// this is different than other "i might not handle this" event interfaces
@@ -66,7 +85,8 @@ struct CRDTNotesEventI {
virtual void onCRDTNSyncEvent(Events::NGCEXT_crdtns_gossip&& e) = 0;
virtual void onCRDTNSyncEvent(Events::NGCEXT_crdtns_gossip_frontier&& e) = 0;
virtual void onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_complete_frontier&& e) = 0;
virtual void onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_op_range&& e) = 0;
virtual void onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_add_range&& e) = 0;
virtual void onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_del&& e) = 0;
virtual void onCRDTNSyncEvent(Events::NGCEXT_crdtns_ops&& e) = 0;
};
@@ -76,18 +96,27 @@ struct CRDTNotesEventI {
class CRDTNotesSync final : public CRDTNotesEventI {
// pull inside????
CRDTNotes& _notes;
Contact3Registry& _cr;
ContactStore4I& _cs;
std::default_random_engine _rng;
std::unordered_map<CRDTNotes::DocID, std::set<Contact3Handle>> _docs_contacts;
std::unordered_map<CRDTNotes::DocID, std::set<ContactHandle4>> _docs_contacts;
struct Peer {
// global frontier
// what we know the peer knows(/gossiped) about
std::unordered_map<decltype(CRDTNotes::Frontier::agent), decltype(CRDTNotes::Frontier::seq)> other_frontier;
};
std::unordered_map<CRDTNotes::DocID, std::map<ContactHandle4, Peer>> _docs_peers;
// queue of unapplied ops, kept here until write lock can be aquired
std::unordered_map<CRDTNotes::DocID, std::vector<CRDTNotes::Doc::Op>> _docs_incoming_ops;
// if a doc is eg new, it is added here
std::set<CRDTNotes::DocID> _gossip_queue;
std::set<CRDTNotes::DocID> _gossip_queue; // TODO: no
std::set<CRDTNotes::DocID> _fetch_frontier_queue;
public:
CRDTNotesSync(CRDTNotes& notes, Contact3Registry& cr);
CRDTNotesSync(CRDTNotes& notes, ContactStore4I& cs);
~CRDTNotesSync(void);
@@ -99,14 +128,14 @@ class CRDTNotesSync final : public CRDTNotesEventI {
// adds a doc and assosiates contact (and self)
// if secret, only self is added (and thats why contact is needed)
std::optional<CRDTNotes::DocID> addNewDoc(Contact3Handle c, bool secret = false);
std::optional<CRDTNotes::DocID> addNewDoc(ContactHandle4 c, bool secret = false);
// adds a doc by id to a contact
// (for gossip or manual add)
bool addDoc(const CRDTNotes::DocID& doc_id, Contact3Handle c);
bool addDoc(const CRDTNotes::DocID& doc_id, ContactHandle4 c);
std::vector<CRDTNotes::DocID> getDocList(void);
std::vector<CRDTNotes::DocID> getDocList(Contact3Handle c);
std::vector<CRDTNotes::DocID> getDocList(ContactHandle4 c);
void merge(const CRDTNotes::DocID& doc_id, std::string_view new_text);
@@ -114,7 +143,8 @@ class CRDTNotesSync final : public CRDTNotesEventI {
void onCRDTNSyncEvent(Events::NGCEXT_crdtns_gossip&& e) override;
void onCRDTNSyncEvent(Events::NGCEXT_crdtns_gossip_frontier&& e) override;
void onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_complete_frontier&& e) override;
void onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_op_range&& e) override;
void onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_add_range&& e) override;
void onCRDTNSyncEvent(Events::NGCEXT_crdtns_fetch_del&& e) override;
void onCRDTNSyncEvent(Events::NGCEXT_crdtns_ops&& e) override;
};

View File

@@ -1,5 +1,6 @@
#include "./crdtnotes_imgui.hpp"
#include <solanaceae/contact/contact_store_i.hpp>
#include <solanaceae/contact/components.hpp>
#include <cstdint>
@@ -8,7 +9,6 @@
#include <imgui.h>
#include <misc/cpp/imgui_stdlib.h>
#include <iostream>
#include <cassert>
namespace detail {
@@ -47,7 +47,13 @@ namespace detail {
} // detail
CRDTNotesImGui::CRDTNotesImGui(CRDTNotesSync& notes_sync, Contact3Registry& cr) : _notes_sync(notes_sync), _cr(cr) {
std::unordered_set<CRDTNotes::DocWriteLock>::iterator CRDTNotesImGui::findLock(const CRDTNotes::DocID& doc_id) {
auto it = _held_locks.begin();
for (; it != _held_locks.end() && it->id != doc_id; it++) {}
return it;
}
CRDTNotesImGui::CRDTNotesImGui(CRDTNotes& notes, CRDTNotesSync& notes_sync, ContactStore4I& cs) : _notes(notes), _notes_sync(notes_sync), _cs(cs) {
}
float CRDTNotesImGui::render(void) {
@@ -58,7 +64,7 @@ float CRDTNotesImGui::render(void) {
}
if (ImGui::BeginPopup("create new doc contact")) {
for (const auto& c : _cr.view<Contact::Components::TagBig>()) {
for (const auto& c : _cs.registry().view<Contact::Components::TagBig>()) {
if (renderContactListContactSmall(c, false)) {
//const auto& self = _cr.get<Contact::Components::Self>(c).self;
//assert(_cr.all_of<Contact::Components::ID>(self));
@@ -76,7 +82,7 @@ float CRDTNotesImGui::render(void) {
//// tox id (id from self)
//self_agent_id
//);
_notes_sync.addNewDoc({_cr, c}, false);
_notes_sync.addNewDoc(_cs.contactHandle(c), false);
//// and open the doc
}
@@ -105,6 +111,7 @@ float CRDTNotesImGui::render(void) {
const std::string docid_str = "Doc " + detail::to_hex(docid);
bool open = true;
ImGui::SetNextWindowSize({200, 200}, ImGuiCond_Appearing);
if (ImGui::Begin(docid_str.c_str(), &open)) {
renderDoc(docid);
}
@@ -119,13 +126,15 @@ float CRDTNotesImGui::render(void) {
}
}
return 1.f;
return 2.f;
}
bool CRDTNotesImGui::renderContactListContactSmall(const Contact3 c, const bool selected) const {
bool CRDTNotesImGui::renderContactListContactSmall(const Contact4 c, const bool selected) const {
std::string label;
label += (_cr.all_of<Contact::Components::Name>(c) ? _cr.get<Contact::Components::Name>(c).name.c_str() : "<unk>");
const auto& cr = _cs.registry();
label += (cr.all_of<Contact::Components::Name>(c) ? cr.get<Contact::Components::Name>(c).name.c_str() : "<unk>");
label += "###";
label += std::to_string(entt::to_integral(c));
@@ -138,17 +147,36 @@ bool CRDTNotesImGui::renderDoc(const CRDTNotes::DocID& doc_id) {
return false;
}
auto lock_it = findLock(doc_id);
bool self_held = lock_it != _held_locks.end();
const bool foreign_held = !self_held && _notes.isWriteLocked(doc_id);
auto text = doc->getText();
if (renderDocText(text)) {
ImGui::InputTextMultiline(
"##doc",
&text,
{-1,-1},
ImGuiInputTextFlags_AllowTabInput |
(foreign_held ? ImGuiInputTextFlags_ReadOnly : ImGuiInputTextFlags_None) |
ImGuiInputTextFlags_CallbackAlways
//cb,
//&text
);
if (!foreign_held && !self_held && (ImGui::IsItemActive() || ImGui::IsItemEdited())) {
// TODO: check
_held_locks.emplace(_notes.writeLockAquire(doc_id).value());
self_held = true;
//std::cout << "!!!! imgui lock aquired\n";
} else if (!foreign_held && self_held && !(ImGui::IsItemActive() || ImGui::IsItemEdited())) {
// release lock
_held_locks.erase(lock_it);
//std::cout << "!!!! imgui lock released\n";
}
if (self_held && ImGui::IsItemEdited()) {
_notes_sync.merge(doc_id, text);
return true;
}
return false;
}
bool CRDTNotesImGui::renderDocText(std::string& text) const {
// TODO: replace with text editor (zep) or visualize stuff??
return ImGui::InputTextMultiline("##doc", &text, {-1,-1}, ImGuiInputTextFlags_AllowTabInput);
}

View File

@@ -1,26 +1,30 @@
#pragma once
#include <solanaceae/crdtnotes/crdtnotes_sync.hpp>
#include <solanaceae/contact/contact_model3.hpp>
#include <solanaceae/contact/fwd.hpp>
#include <set>
#include <unordered_set>
class CRDTNotesImGui {
CRDTNotes& _notes;
CRDTNotesSync& _notes_sync;
Contact3Registry& _cr;
ContactStore4I& _cs;
bool _show_global_list {true};
std::set<CRDTNotes::DocID> _open_docs;
std::unordered_set<CRDTNotes::DocWriteLock> _held_locks;
std::unordered_set<CRDTNotes::DocWriteLock>::iterator findLock(const CRDTNotes::DocID& doc_id);
public:
CRDTNotesImGui(CRDTNotesSync& notes_sync, Contact3Registry& cr);
CRDTNotesImGui(CRDTNotes& notes, CRDTNotesSync& notes_sync, ContactStore4I& cs);
float render(void);
bool renderContactListContactSmall(const Contact3 c, const bool selected) const;
bool renderContactListContactSmall(const Contact4 c, const bool selected) const;
bool renderDoc(const CRDTNotes::DocID& doc_id);
bool renderDocText(std::string& text) const;
};

View File

@@ -1,5 +1,6 @@
#include "./crdtnotes_toxsync.hpp"
#include <solanaceae/contact/contact_store_i.hpp>
#include <solanaceae/toxcore/tox_interface.hpp>
#include <solanaceae/tox_contacts/components.hpp>
@@ -15,6 +16,7 @@ enum class NGCEXT_Event : uint8_t {
// - array [
// - AgentID
// - seq (frontier)
// - del_num
// - ]
CRDTN_GOSSIP_FRONTIER,
@@ -25,7 +27,11 @@ enum class NGCEXT_Event : uint8_t {
// - AgentID
// - seq_from
// - seq_to
CRDTN_FETCH_OP_RANGE,
CRDTN_FETCH_ADD_RANGE,
// - DocID
// - AgentID
CRDTN_FETCH_DEL,
// - DocID
// - array [
@@ -39,11 +45,11 @@ enum class NGCEXT_Event : uint8_t {
CRDTNotesToxSync::CRDTNotesToxSync(
CRDTNotesEventI& notes_sync,
Contact3Registry& cr,
ContactStore4I& cs,
ToxI& t,
ToxEventProviderI& tep,
ToxContactModel2& tcm
) : _notes_sync(notes_sync), _cr(cr), _t(t), _tep_sr(tep.newSubRef(this)), _tcm(tcm) {
) : _notes_sync(notes_sync), _cs(cs), _t(t), _tep_sr(tep.newSubRef(this)), _tcm(tcm) {
// TODO: non groups
// should be called for every peer (except self)
@@ -58,13 +64,13 @@ CRDTNotesToxSync::CRDTNotesToxSync(
CRDTNotesToxSync::~CRDTNotesToxSync(void) {
// TODO: find a better way to remove dangling pointers
std::vector<Contact3> to_remove_self;
_cr.view<CRDTNotesContactSyncModelI*>().each([&to_remove_self, this](Contact3 c, const auto* csm) {
std::vector<Contact4> to_remove_self;
_cs.registry().view<CRDTNotesContactSyncModelI*>().each([&to_remove_self, this](Contact4 c, const auto* csm) {
if (this == csm) {
to_remove_self.push_back(c);
}
});
_cr.remove<CRDTNotesContactSyncModelI*>(to_remove_self.cbegin(), to_remove_self.cend());
_cs.registry().remove<CRDTNotesContactSyncModelI*>(to_remove_self.cbegin(), to_remove_self.cend());
}
float CRDTNotesToxSync::iterate(float time_delta) {
@@ -73,7 +79,7 @@ float CRDTNotesToxSync::iterate(float time_delta) {
}
void CRDTNotesToxSync::SendGossip(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id
) {
if (!c.all_of<Contact::Components::ToxGroupPeerEphemeral>()) {
@@ -98,9 +104,9 @@ void CRDTNotesToxSync::SendGossip(
}
void CRDTNotesToxSync::SendGossip(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const std::vector<CRDTNotes::Frontier>& selected_frontier
const std::vector<CRDTNotes::Frontier>& frontier
) {
if (!c.all_of<Contact::Components::ToxGroupPeerEphemeral>()) {
return;
@@ -116,7 +122,7 @@ void CRDTNotesToxSync::SendGossip(
}
// +32
for (const auto& [f_id, f_seq] : selected_frontier) {
for (const auto& [f_id, f_seq, del_num] : frontier) {
for (const uint8_t v : f_id) {
pkg.push_back(v);
}
@@ -126,8 +132,13 @@ void CRDTNotesToxSync::SendGossip(
pkg.push_back((f_seq >> i*8) & 0xff);
}
// +8
for (size_t i = 0; i < sizeof(del_num); i++) {
pkg.push_back((del_num >> i*8) & 0xff);
}
// +8
}
// +40
// +48
// send
const auto& gp = c.get<Contact::Components::ToxGroupPeerEphemeral>();
@@ -139,7 +150,7 @@ void CRDTNotesToxSync::SendGossip(
}
void CRDTNotesToxSync::SendFetchCompleteFrontier(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id
) {
if (!c.all_of<Contact::Components::ToxGroupPeerEphemeral>()) {
@@ -163,8 +174,8 @@ void CRDTNotesToxSync::SendFetchCompleteFrontier(
);
}
void CRDTNotesToxSync::SendFetchOps(
Contact3Handle c,
void CRDTNotesToxSync::SendFetchAddRange(
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const CRDTNotes::CRDTAgent& agent,
const uint64_t seq_from,
@@ -176,7 +187,7 @@ void CRDTNotesToxSync::SendFetchOps(
std::vector<uint8_t> pkg;
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::CRDTN_FETCH_OP_RANGE));
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::CRDTN_FETCH_ADD_RANGE));
for (const uint8_t v : doc_id) {
pkg.push_back(v);
@@ -206,8 +217,37 @@ void CRDTNotesToxSync::SendFetchOps(
);
}
void CRDTNotesToxSync::SendFetchDel(
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const CRDTNotes::CRDTAgent& agent
) {
if (!c.all_of<Contact::Components::ToxGroupPeerEphemeral>()) {
return;
}
std::vector<uint8_t> pkg;
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::CRDTN_FETCH_DEL));
for (const uint8_t v : doc_id) {
pkg.push_back(v);
}
for (const uint8_t v : agent) {
pkg.push_back(v);
}
const auto& gp = c.get<Contact::Components::ToxGroupPeerEphemeral>();
_t.toxGroupSendCustomPrivatePacket(
gp.group_number, gp.peer_number,
true,
pkg
);
}
void CRDTNotesToxSync::SendOps(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const std::vector<CRDTNotes::Doc::Op>& ops
) {
@@ -227,6 +267,7 @@ void CRDTNotesToxSync::SendOps(
// this is very inefficent
// a full add op is 124bytes like this
// a full del op is 41bytes
for (const auto& op : ops) {
if(std::holds_alternative<CRDTNotes::Doc::OpAdd>(op)) {
const auto& add_op = std::get<CRDTNotes::Doc::OpAdd>(op);
@@ -288,7 +329,7 @@ void CRDTNotesToxSync::SendOps(
}
bool CRDTNotesToxSync::parse_crdtn_gossip(
Contact3Handle c,
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool // dont care private
) {
@@ -313,7 +354,7 @@ bool CRDTNotesToxSync::parse_crdtn_gossip(
}
bool CRDTNotesToxSync::parse_crdtn_gossip_frontier(
Contact3Handle c,
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool // dont care private
) {
@@ -331,7 +372,7 @@ bool CRDTNotesToxSync::parse_crdtn_gossip_frontier(
while (curser < data_size) {
CRDTNotes::Frontier new_f;
_DATA_HAVE(new_f.agent.size() * sizeof(CRDTNotes::CRDTAgent::value_type) + sizeof(new_f.seq), std::cerr << "NGCEXT: packet malformed, not enough data for forntier\n"; return false;)
_DATA_HAVE(new_f.agent.size() * sizeof(CRDTNotes::CRDTAgent::value_type) + sizeof(new_f.seq) + sizeof(new_f.del_num), std::cerr << "NGCEXT: packet malformed, not enough data for frontier\n"; return false;)
for (size_t i = 0; i < new_f.agent.size(); i++, curser++) {
new_f.agent[i] = data[curser];
@@ -342,7 +383,12 @@ bool CRDTNotesToxSync::parse_crdtn_gossip_frontier(
new_f.seq |= uint64_t(data[curser]) << i*8;
}
e.selected_frontier.emplace_back(std::move(new_f));
new_f.del_num = 0;
for (size_t i = 0; i < sizeof(new_f.del_num); i++, curser++) {
new_f.del_num |= uint64_t(data[curser]) << i*8;
}
e.frontier.emplace_back(std::move(new_f));
}
std::cout << "CRDTN gossip_frontier parsed\n";
@@ -351,7 +397,7 @@ bool CRDTNotesToxSync::parse_crdtn_gossip_frontier(
}
bool CRDTNotesToxSync::parse_crdtn_fetch_complete_frontier(
Contact3Handle c,
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool // dont care private
) {
@@ -370,12 +416,12 @@ bool CRDTNotesToxSync::parse_crdtn_fetch_complete_frontier(
return true;
}
bool CRDTNotesToxSync::parse_crdtn_fetch_op_range(
Contact3Handle c,
bool CRDTNotesToxSync::parse_crdtn_fetch_add_range(
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool // dont care private
) {
Events::NGCEXT_crdtns_fetch_op_range e;
Events::NGCEXT_crdtns_fetch_add_range e;
e.c = c;
size_t curser = 0;
@@ -402,13 +448,38 @@ bool CRDTNotesToxSync::parse_crdtn_fetch_op_range(
e.seq_to |= uint64_t(data[curser]) << i*8;
}
std::cout << "CRDTN fetch_op_range parsed\n";
std::cout << "CRDTN fetch_add_range parsed\n";
_notes_sync.onCRDTNSyncEvent(std::move(e));
return true;
}
bool CRDTNotesToxSync::parse_crdtn_fetch_del(
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool // dont care private
) {
Events::NGCEXT_crdtns_fetch_del e;
e.c = c;
size_t curser = 0;
_DATA_HAVE(e.doc_id.size() * sizeof(decltype(e.doc_id)::value_type), std::cerr << "NGCEXT: packet too small, missing doc_id\n"; return false;)
for (size_t i = 0; i < e.doc_id.size(); i++, curser++) {
e.doc_id[i] = data[curser];
}
_DATA_HAVE(e.agent.size() * sizeof(decltype(e.agent)::value_type), std::cerr << "NGCEXT: packet too small, missing agent\n"; return false;)
for (size_t i = 0; i < e.agent.size(); i++, curser++) {
e.agent[i] = data[curser];
}
std::cout << "CRDTN fetch_del parsed\n";
_notes_sync.onCRDTNSyncEvent(std::move(e));
return true;
}
bool CRDTNotesToxSync::parse_crdtn_ops(
Contact3Handle c,
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool // dont care private
) {
@@ -530,8 +601,10 @@ bool CRDTNotesToxSync::handlePacket(
return parse_crdtn_gossip_frontier(c, data+1, data_size-1, _private);
case NGCEXT_Event::CRDTN_FETCH_COMPLETE_FRONTIER:
return parse_crdtn_fetch_complete_frontier(c, data+1, data_size-1, _private);
case NGCEXT_Event::CRDTN_FETCH_OP_RANGE:
return parse_crdtn_fetch_op_range(c, data+1, data_size-1, _private);
case NGCEXT_Event::CRDTN_FETCH_ADD_RANGE:
return parse_crdtn_fetch_add_range(c, data+1, data_size-1, _private);
case NGCEXT_Event::CRDTN_FETCH_DEL:
return parse_crdtn_fetch_del(c, data+1, data_size-1, _private);
case NGCEXT_Event::CRDTN_OPS:
return parse_crdtn_ops(c, data+1, data_size-1, _private);
default:

View File

@@ -1,9 +1,8 @@
#pragma once
#include "solanaceae/crdtnotes/crdtnotes_sync.hpp"
#include <solanaceae/crdtnotes/crdtnotes.hpp>
#include <solanaceae/crdtnotes/crdtnotes_sync.hpp>
#include <solanaceae/crdtnotes/crdtnotes_contact_sync_model.hpp>
#include <solanaceae/contact/contact_model3.hpp>
#include <solanaceae/contact/fwd.hpp>
#include <solanaceae/toxcore/tox_event_interface.hpp>
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
@@ -14,7 +13,7 @@ struct ToxEventProviderI;
// implements CRDTNotesContactSyncModelI and attaches itself to tox contacts
class CRDTNotesToxSync : public CRDTNotesContactSyncModelI, public ToxEventI {
CRDTNotesEventI& _notes_sync;
Contact3Registry& _cr;
ContactStore4I& _cs;
ToxI& _t;
ToxEventProviderI::SubscriptionReference _tep_sr;
ToxContactModel2& _tcm;
@@ -22,7 +21,7 @@ class CRDTNotesToxSync : public CRDTNotesContactSyncModelI, public ToxEventI {
public:
CRDTNotesToxSync(
CRDTNotesEventI& notes_sync,
Contact3Registry& cr,
ContactStore4I& cs,
ToxI& t,
ToxEventProviderI& tep,
ToxContactModel2& tcm
@@ -33,58 +32,69 @@ class CRDTNotesToxSync : public CRDTNotesContactSyncModelI, public ToxEventI {
public: // sync api
void SendGossip(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id
) override;
void SendGossip(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const std::vector<CRDTNotes::Frontier>& selected_frontier
const std::vector<CRDTNotes::Frontier>& frontier
) override;
void SendFetchCompleteFrontier(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id
) override;
void SendFetchOps(
Contact3Handle c,
void SendFetchAddRange(
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const CRDTNotes::CRDTAgent& agent,
const uint64_t seq_from,
const uint64_t seq_to
) override;
void SendFetchDel(
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const CRDTNotes::CRDTAgent& agent
) override;
void SendOps(
Contact3Handle c,
ContactHandle4 c,
const CRDTNotes::DocID& doc_id,
const std::vector<CRDTNotes::Doc::Op>&
) override;
private:
bool parse_crdtn_gossip(
Contact3Handle c,
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool _private
);
bool parse_crdtn_gossip_frontier(
Contact3Handle c,
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool _private
);
bool parse_crdtn_fetch_complete_frontier(
Contact3Handle c,
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool _private
);
bool parse_crdtn_fetch_op_range(
Contact3Handle c,
bool parse_crdtn_fetch_add_range(
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool _private
);
bool parse_crdtn_fetch_del(
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool _private
);
bool parse_crdtn_ops(
Contact3Handle c,
ContactHandle4 c,
const uint8_t* data, size_t data_size,
bool _private
);