List 与 MovableList
Loro 提供两种列表:List 与 MovableList。List 支持插入、删除;MovableList 额外支持 set 与 move。
使用插入/删除可在 List 中模拟 set/move,但在并发场景会失效:同一元素被同时 set 或 move 时,模拟方案会删除原元素并插入两个新元素,结果不符合预期。
MovableList 可以正确处理这些情况,不过需要额外开销:仅执行插入/删除时,它在编解码上大约慢 80%,内存开销约高 50%。
两者都基于 Fugue 实现最大非交错性;MovableList 的移动能力额外使用 Moving Elements in List CRDTs 算法。
基本用法
List
const = new ();
.("1");
const = .("list");
.(0);
.(1);
.(2);
const : = .({ : "snapshot" });
const = .();
.("2");
const = .("list");
{
// docA、docB 并发修改索引 2
.(2, 1);
.(2, 9);
(.()).({ : [0, 1, 9] });
.(2, 1);
.(2, 8);
(.()).({ : [0, 1, 8] });
}
{
.(.({ : "update", : .() }));
.(.({ : "update", : .() }));
}
(.()).({ : [0, 1, 8, 9] });
(.()).({ : [0, 1, 8, 9] });MovableList
const = new ();
.("1");
const = .("list");
.(0);
.(1);
.(2);
const : = .({ : "snapshot" });
const = .();
.("2");
const = .("list");
{
.(2, 8);
(.()).({ : [0, 1, 8] });
.(2, 9);
(.()).({ : [0, 1, 9] });
}
{
.(.({ : "update", : .() }));
.(.({ : "update", : .() }));
}
// 因 docB 的 peerId 更大,最终收敛为 [0, 1, 9]
(.()).({ : [0, 1, 9] });
(.()).({ : [0, 1, 9] });
{
.(0, 2);
.(0, 1);
(.()).({ : [1, 9, 0] });
(.()).({ : [1, 0, 9] });
}
{
.(.({ : "update", : .() }));
.(.({ : "update", : .() }));
}
(.()).({ : [1, 0, 9] });
(.()).({ : [1, 0, 9] });List 上的光标
List 的光标会随列表变动:前方插入 → 光标右移;前方删除 → 左移;后方改动则不受影响。可用于稳定表示段落等结构的选区。
const = new ();
.("1");
const = .("list");
.("Hello");
.("World");
const = .(1)!;
.(.()); // { peer: "1", counter: 1 }
const : = .();
const : = .({ : "snapshot" });
const = new ();
.("2");
const = .("list");
.();
.(0, "Foo");
.(.()); // { list: ["Foo", "Hello", "World"] }
const = .();
{
const = .();
.(.); // 2
}
.(1, "Bar");
.(.()); // { list: ["Foo", "Bar", "Hello", "World"] }
{
const = .();
.(.); // 3
}
.(3, 1);
.(.()); // { list: ["Foo", "Bar", "Hello"] }
{
const = .();
.(.); // 3
.(.); // { peer: "2", counter: 1 }
const : = .!;
.(.()); // undefined(位于列表末尾)
.(.()); // 1(位于右端)
const = .();
.(.); // 3
.(.); // undefined
}