├── public ├── js │ └── .gitkeep └── assets │ ├── img │ ├── call.png │ ├── shhh.ico │ ├── favicon.ico │ ├── myLipsAreSealed.ico │ └── conclave-mask-small.ico │ └── fonts │ ├── InputSans-Light.ttf │ └── InputSans-Medium.ttf ├── Procfile ├── performance ├── logs │ └── .gitkeep ├── utilLinear.js ├── comparisons │ └── linearArray │ │ ├── tripleBase │ │ ├── 2017-11-17 5:41:7.log │ │ ├── 2017-11-17 5:40:56.log │ │ └── 2017-11-17 1:44:28.log │ │ ├── doubleBase │ │ ├── 2017-11-17 5:39:6.log │ │ ├── 2017-11-17 5:38:54.log │ │ └── 2017-11-17 1:29:26.log │ │ └── constantBase │ │ ├── 2017-11-17 2:26:13.log │ │ └── 2017-11-17 5:19:28.log ├── scriptLinear.js ├── script.js └── util.js ├── _config.yml ├── .dockerignore ├── .babelrc ├── .gitignore ├── Dockerfile ├── views ├── bots.pug ├── idGraph.pug ├── timeGraph.pug ├── arraysGraph.pug ├── about.pug ├── index.pug └── layout.pug ├── Makefile ├── spec ├── support │ └── jasmine.json ├── sortedArraySpec.js ├── versionSpec.js ├── identifierSpec.js ├── editorSpec.js ├── charSpec.js └── versionVectorSpec.js ├── lib ├── sortedArray.js ├── identifier.js ├── hashAlgo.js ├── char.js ├── main.js ├── version.js ├── remoteCursor.js ├── demo.js ├── versionVector.js ├── idGraph.js ├── userBot.js ├── timeGraph.js ├── cursorNames.js ├── arraysGraph.js ├── utilLinear.js ├── crdtLinear.js ├── cssColors.js ├── broadcast.js ├── util.js └── editor.js ├── webpack.config.js ├── app.js ├── LICENSE.txt ├── package.json └── README.md /public/js/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start 2 | -------------------------------------------------------------------------------- /performance/logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-3"], 3 | "plugins": [] 4 | } 5 | -------------------------------------------------------------------------------- /public/assets/img/call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conclave-team/conclave/HEAD/public/assets/img/call.png -------------------------------------------------------------------------------- /public/assets/img/shhh.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conclave-team/conclave/HEAD/public/assets/img/shhh.ico -------------------------------------------------------------------------------- /public/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conclave-team/conclave/HEAD/public/assets/img/favicon.ico -------------------------------------------------------------------------------- /public/assets/img/myLipsAreSealed.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conclave-team/conclave/HEAD/public/assets/img/myLipsAreSealed.ico -------------------------------------------------------------------------------- /public/assets/fonts/InputSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conclave-team/conclave/HEAD/public/assets/fonts/InputSans-Light.ttf -------------------------------------------------------------------------------- /public/assets/fonts/InputSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conclave-team/conclave/HEAD/public/assets/fonts/InputSans-Medium.ttf -------------------------------------------------------------------------------- /public/assets/img/conclave-mask-small.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conclave-team/conclave/HEAD/public/assets/img/conclave-mask-small.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/js/*.js 3 | /public/js/*.txt 4 | /build 5 | debug.js 6 | .DS_Store 7 | /performance/logs/* 8 | !/performance/logs/.gitkeep 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | WORKDIR /usr/src/app 4 | COPY package*.json ./ 5 | RUN npm install 6 | 7 | COPY . . 8 | 9 | RUN npm run build 10 | 11 | EXPOSE 3000 12 | 13 | -------------------------------------------------------------------------------- /views/bots.pug: -------------------------------------------------------------------------------- 1 | extends layout.pug 2 | 3 | block link 4 | p.disappear Sharing Link: 5 | a.link(id='myLink' target="_blank") 6 | 7 | block scripts 8 | script(src='js/bots.js') 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build-image: 2 | docker build -t conclave . 3 | 4 | run-local: build-image server 5 | 6 | server: 7 | docker run --rm -p 3000:3000 -e DEBUG=express:* conclave npm start 8 | 9 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js", 8 | "../node_modules/babel-register/lib/node.js" 9 | ], 10 | "stopSpecOnExpectationFailure": false, 11 | "random": false 12 | } 13 | -------------------------------------------------------------------------------- /views/idGraph.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title='Linear Array CRDT Performance' 5 | script(src="https://cdn.plot.ly/plotly-latest.min.js") 6 | body 7 | div(id='g0') 8 | div(id='g1') 9 | div(id='g2') 10 | div(id='g3') 11 | script(src='js/idGraph.js') 12 | -------------------------------------------------------------------------------- /views/timeGraph.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title='Linear Array CRDT Performance' 5 | script(src="https://cdn.plot.ly/plotly-latest.min.js") 6 | body 7 | div(id='g0') 8 | div(id='g1') 9 | div(id='g2') 10 | div(id='g3') 11 | script(src='js/timegraph.js') 12 | -------------------------------------------------------------------------------- /views/arraysGraph.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title='Linear Array versus Array-of-Arrays CRDT Performance' 5 | script(src="https://cdn.plot.ly/plotly-latest.min.js") 6 | body 7 | div(id='g0') 8 | div(id='g1') 9 | div(id='g2') 10 | div(id='g3') 11 | script(src='js/arraysGraph.js') 12 | -------------------------------------------------------------------------------- /lib/sortedArray.js: -------------------------------------------------------------------------------- 1 | import sorted from 'sorted-cmp-array'; 2 | 3 | // Extending SortedArray functionality from 'sorted-cmp-array'. 4 | // Adding a 'get' method for retrieving elements. 5 | class SortedArray extends sorted { 6 | constructor(compareFn) { 7 | super(compareFn); 8 | } 9 | 10 | get(idx) { 11 | return this.arr[idx]; 12 | } 13 | } 14 | 15 | export default SortedArray; 16 | -------------------------------------------------------------------------------- /spec/sortedArraySpec.js: -------------------------------------------------------------------------------- 1 | import SortedArray from '../lib/sortedArray'; 2 | 3 | describe('SortedArray', () => { 4 | describe('get', () => { 5 | it('returns the element at the specified index', () => { 6 | const sortedArray = new SortedArray((a, b) => a - b); 7 | sortedArray.insert(2); 8 | sortedArray.insert(1); 9 | 10 | expect(sortedArray.get(0)).toBe(1); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | var glob = require("glob"); 3 | 4 | module.exports = { 5 | context: path.resolve(__dirname, 'lib'), 6 | entry: { 7 | main: './main.js', 8 | demo: './demo.js', 9 | bots: './userBot.js', 10 | timegraph: './timeGraph.js', 11 | arraysGraph: './arraysGraph.js', 12 | idGraph: './idGraph.js' 13 | }, 14 | output: { 15 | filename: '[name].js', 16 | path: path.resolve(__dirname, 'public/js'), 17 | }, 18 | }; -------------------------------------------------------------------------------- /lib/identifier.js: -------------------------------------------------------------------------------- 1 | class Identifier { 2 | constructor(digit, siteId) { 3 | this.digit = digit; 4 | this.siteId = siteId; 5 | } 6 | 7 | // Compare identifiers using their digit value with siteID as the tiebreaker 8 | // If identifers are equal, return 0 9 | compareTo(otherId) { 10 | if (this.digit < otherId.digit) { 11 | return -1; 12 | } else if (this.digit > otherId.digit) { 13 | return 1; 14 | } else { 15 | if (this.siteId < otherId.siteId) { 16 | return -1; 17 | } else if (this.siteId > otherId.siteId) { 18 | return 1; 19 | } else { 20 | return 0; 21 | } 22 | } 23 | } 24 | } 25 | 26 | export default Identifier; 27 | -------------------------------------------------------------------------------- /lib/hashAlgo.js: -------------------------------------------------------------------------------- 1 | function hashAlgo(input, collection) { 2 | // const alphabet = 'abcdefghijklmnopqrstuvwxyz'; 3 | // const filteredInputArray = input.toLowerCase().replace(/[a-z\-]/g, '').split(''); 4 | // const sum = filteredInputArray.reduce((acc, num) => acc + Number(num), 0); 5 | 6 | // return Math.floor((sum * 13) / 7) % collection.length; 7 | 8 | const justNums = input.toLowerCase().replace(/[a-z\-]/g, ''); 9 | return Math.floor(justNums * 13) % collection.length 10 | } 11 | 12 | function generateItemFromHash(siteId, collection) { 13 | const hashIdx = hashAlgo(siteId, collection); 14 | 15 | return collection[hashIdx]; 16 | } 17 | 18 | export { 19 | hashAlgo, 20 | generateItemFromHash 21 | } 22 | -------------------------------------------------------------------------------- /lib/char.js: -------------------------------------------------------------------------------- 1 | class Char { 2 | constructor(value, counter, siteId, identifiers) { 3 | this.position = identifiers; 4 | this.counter = counter; 5 | this.siteId = siteId; 6 | this.value = value; 7 | } 8 | 9 | compareTo(otherChar) { 10 | let comp, id1, id2; 11 | const pos1 = this.position; 12 | const pos2 = otherChar.position; 13 | 14 | for (let i = 0; i < Math.min(pos1.length, pos2.length); i++) { 15 | id1 = pos1[i]; 16 | id2 = pos2[i]; 17 | comp = id1.compareTo(id2); 18 | 19 | if (comp !== 0) { 20 | return comp; 21 | } 22 | } 23 | 24 | if (pos1.length < pos2.length) { 25 | return -1; 26 | } else if (pos1.length > pos2.length) { 27 | return 1; 28 | } else { 29 | return 0; 30 | } 31 | } 32 | } 33 | 34 | export default Char; 35 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | import Peer from 'peerjs'; 2 | import SimpleMDE from 'simplemde'; 3 | 4 | import Controller from './controller'; 5 | import Broadcast from './broadcast'; 6 | import Editor from './editor'; 7 | 8 | if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) { 9 | 10 | } else { 11 | new Controller( 12 | (location.search.slice(1) || '0'), 13 | location.origin, 14 | new Peer({ 15 | debug: 3 16 | }), 17 | new Broadcast(), 18 | new Editor(new SimpleMDE({ 19 | placeholder: "Share the link to invite collaborators to your document.", 20 | spellChecker: false, 21 | toolbar: false, 22 | autofocus: false, 23 | indentWithTabs: true, 24 | tabSize: 4, 25 | indentUnit: 4, 26 | lineWrapping: false, 27 | shortCuts: [] 28 | })) 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /views/about.pug: -------------------------------------------------------------------------------- 1 | extends layout.pug 2 | 3 | block nav 4 | .start 5 | a.link(href='/') New Document 6 | .case-study 7 | a.link(href='https://conclave-team.github.io/conclave-site/' target="_blank") Case Study 8 | .team 9 | a.link(href='https://conclave-team.github.io/conclave-site/team/' target="_blank") Our Team 10 | .github 11 | a.link(href='https://github.com/conclave-team/conclave' target="_blank") Fork on GitHub 12 | block link 13 | p.sharing-link.disappear 14 | a.link(id='myLink' target="_blank") Sharing Link 15 | span(id="myLinkInput" class="aside disappear") 16 | span(class="copy-container" data-tooltip="Copy to Clipboard") 17 | i(data-feather="copy" class="copy-link" color="rgb(17, 117, 232)") 18 | span(class="copy-status") Copied! 19 | 20 | block scripts 21 | script(src='js/demo.js') 22 | -------------------------------------------------------------------------------- /views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout.pug 2 | 3 | block nav 4 | .about 5 | a.link(href='/about' target="_blank") What is Conclave? 6 | .case-study 7 | a.link(href='https://conclave-team.github.io/conclave-site/' target="_blank") Case Study 8 | .team 9 | a.link(href='https://conclave-team.github.io/conclave-site/team/' target="_blank") Our Team 10 | .github 11 | a.link(href='https://github.com/conclave-team/conclave' target="_blank") Fork on GitHub 12 | 13 | block link 14 | p.sharing-link 15 | a.link(id='myLink' target="_blank") Sharing Link 16 | span(id="myLinkInput" class="aside disappear") 17 | span(class="copy-container" data-tooltip="Copy to Clipboard") 18 | i(data-feather="copy" class="copy-link" color="rgb(17, 117, 232)") 19 | span(class="copy-status") Copied! 20 | 21 | block scripts 22 | script(src='js/main.js') 23 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const app = express(); 4 | const port = process.env.PORT || 3000; 5 | 6 | app.use(express.static('public')); 7 | app.set('views', path.join(__dirname, 'views')); 8 | app.set('view engine', 'pug'); 9 | 10 | app.get('/', function (req, res) { 11 | res.render('index', {title: 'Conclave'}); 12 | }); 13 | 14 | app.get('/about', function (req, res) { 15 | res.render('about', {title: 'About'}); 16 | }); 17 | 18 | app.get('/bots', function(req, res) { 19 | res.render('bots', {title: 'Talk to Bots'}); 20 | }); 21 | 22 | app.get('/idLength', function (req, res) { 23 | res.render('idGraph'); 24 | }); 25 | 26 | app.get('/opTime', function (req, res) { 27 | res.render('timeGraph'); 28 | }) 29 | 30 | app.get('/arraysGraph', function (req, res) { 31 | res.render('arraysGraph'); 32 | }) 33 | 34 | var srv = app.listen(port, function() { 35 | console.log('Listening on '+port) 36 | }) 37 | 38 | app.use('/peerjs', require('peer').ExpressPeerServer(srv, { 39 | debug: true 40 | })) 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Conclave Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/version.js: -------------------------------------------------------------------------------- 1 | // Class that wraps the information about each version. 2 | // exceptions are a set of counters for operations that our local CRDT has not 3 | // seen or integrated yet. Waiting for these operations. 4 | class Version { 5 | constructor(siteId) { 6 | this.siteId = siteId; 7 | this.counter = 0; 8 | this.exceptions = []; 9 | } 10 | 11 | // Update a site's version based on the incoming operation that was processed 12 | // If the incomingCounter is less than we had previously processed, we can remove it from the exceptions 13 | // Else if the incomingCounter is the operation immediately after the last one we procesed, we just increment our counter to reflect that 14 | // Else, add an exception for each counter value that we haven't seen yet, and update our counter to match 15 | update(version) { 16 | const incomingCounter = version.counter; 17 | 18 | if (incomingCounter <= this.counter) { 19 | const index = this.exceptions.indexOf(incomingCounter); 20 | this.exceptions.splice(index, 1); 21 | } else if (incomingCounter === this.counter + 1) { 22 | this.counter = this.counter + 1; 23 | } else { 24 | for (let i = this.counter + 1; i < incomingCounter; i++) { 25 | this.exceptions.push(i); 26 | } 27 | this.counter = incomingCounter; 28 | } 29 | } 30 | } 31 | 32 | export default Version; 33 | -------------------------------------------------------------------------------- /spec/versionSpec.js: -------------------------------------------------------------------------------- 1 | import Version from '../lib/version'; 2 | 3 | describe('Version', () => { 4 | let siteId, version; 5 | 6 | beforeEach(() => { 7 | siteId = Math.floor(Math.random() * 1000); 8 | version = new Version(siteId); 9 | }); 10 | 11 | describe('constructor', () => { 12 | it('initializes with counter at 0', () => { 13 | expect(version.counter).toBe(0); 14 | }); 15 | }); 16 | 17 | describe('update', () => { 18 | it('increments counter by one it counter is greater by 1', () => { 19 | version.update({sideId: siteId, counter: 1}); 20 | 21 | expect(version.counter).toBe(1); 22 | }); 23 | 24 | it('does not increment counter if remote counter is less than current', () => { 25 | version.update({sideId: siteId, counter: -1}); 26 | 27 | expect(version.counter).toBe(0); 28 | }); 29 | 30 | it('does not increment counter if remote counter is equal to current', () => { 31 | version.update({sideId: siteId, counter: 0}); 32 | 33 | expect(version.counter).toBe(0); 34 | }); 35 | 36 | it('creates exceptions if remote counter is greater than current by more than 1', () => { 37 | version.update({sideId: siteId, counter: 2}); 38 | 39 | expect(version.exceptions.includes(1)).toBe(true); 40 | }); 41 | 42 | it('removes exceptions if remote counter is less than current and exists in exceptions', () => { 43 | version.update({sideId: siteId, counter: 2}); 44 | expect(version.exceptions.includes(1)).toBe(true); 45 | 46 | version.update({sideId: siteId, counter: 1}); 47 | expect(version.exceptions.includes(1)).toBe(false); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conclave", 3 | "version": "1.0.0", 4 | "description": "Peer-to-peer collaborative text editor", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "jasmine", 8 | "start": "node app.js", 9 | "build": "webpack -c ./webpack.config.js", 10 | "compile": "browserify build/main.js -o public/js/bundle.js && browserify build/demo.js -o public/js/demo-bundle.js && browserify build/idGraph.js -o public/js/idGraph-bundle.js && browserify build/timeGraph.js -o public/js/timeGraph-bundle.js && browserify build/arraysGraph.js -o public/js/arraysGraph-bundle.js", 11 | "local": "npm run build && npm start", 12 | "perform": "babel-node performance/script.js", 13 | "perform-linear": "babel-node performance/scriptLinear.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/Super-NES/conclave.git" 18 | }, 19 | "author": "Super-NES", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/Super-NES/conclave/issues" 23 | }, 24 | "homepage": "https://github.com/Super-NES/conclave#readme", 25 | "dependencies": { 26 | "acorn": "^6.4.2", 27 | "express": "^4.17.1", 28 | "feather-icons": "^4.28.0", 29 | "jquery": "^3.6.0", 30 | "peer": "^0.6.1", 31 | "peerjs": "^1.3.1", 32 | "pug": "^3.0.1", 33 | "rxjs": "^5.5.12", 34 | "simplemde": "^1.11.2", 35 | "sorted-cmp-array": "^2.0.1", 36 | "uuid": "^3.4.0" 37 | }, 38 | "devDependencies": { 39 | "babel-cli": "^6.26.0", 40 | "babel-core": "^6.26.3", 41 | "babel-preset-es2015": "^6.24.1", 42 | "babel-preset-stage-3": "^6.24.1", 43 | "babel-register": "^6.26.0", 44 | "jasmine": "^2.99.0", 45 | "browserify": "^16.5.2", 46 | "jsdom": "^11.12.0", 47 | "webpack": "^5.38.1", 48 | "webpack-cli": "^4.7.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /spec/identifierSpec.js: -------------------------------------------------------------------------------- 1 | import Identifier from '../lib/identifier'; 2 | 3 | describe("Identifier", () => { 4 | describe("compareTo", () => { 5 | it("compares itself to an id with a larger digit", () => { 6 | const siteId1 = 1; 7 | const siteId2 = 20; 8 | const id1 = new Identifier(1, siteId1); 9 | const id2 = new Identifier(2, siteId2); 10 | 11 | const comparator = id1.compareTo(id2); 12 | expect(comparator).toBe(-1); 13 | }); 14 | 15 | it("compares itself to an id with a smaller digit", () => { 16 | const siteId1 = 1; 17 | const siteId2 = 20; 18 | const id1 = new Identifier(2, siteId1); 19 | const id2 = new Identifier(1, siteId2); 20 | 21 | const comparator = id1.compareTo(id2); 22 | expect(comparator).toBe(1); 23 | }); 24 | 25 | it("compares itself to an id with a larger siteId", () => { 26 | const siteId1 = 2; 27 | const siteId2 = 1; 28 | const id1 = new Identifier(1, siteId1); 29 | const id2 = new Identifier(2, siteId2); 30 | 31 | const comparator = id1.compareTo(id2); 32 | expect(comparator).toBe(-1); 33 | }); 34 | 35 | it("compares itself to an id with a smaller siteId", () => { 36 | const siteId1 = 2; 37 | const siteId2 = 1; 38 | const id1 = new Identifier(2, siteId1); 39 | const id2 = new Identifier(1, siteId2); 40 | 41 | const comparator = id1.compareTo(id2); 42 | expect(comparator).toBe(1); 43 | }); 44 | 45 | it("compares itself to an id with the same digit and site", () => { 46 | const siteId1 = 1; 47 | const siteId2 = 1; 48 | const id1 = new Identifier(1, siteId1); 49 | const id2 = new Identifier(1, siteId2); 50 | 51 | const comparator = id1.compareTo(id2); 52 | expect(comparator).toBe(0); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Conclave Logo](/public/assets/img/conclave-mask-small.ico) 2 | 3 | # Conclave: Collaborate in private 4 | 5 | # No Longer Maintained 6 | 7 | Conclave was made for fun and educational purposes but it is no longer actively maintained. The creators have since moved onto other projects and work. It will remain open so that developers can ask questions and fork it. The demo at [https://conclave.tech](https://conclave.tech) will also stay up. However, feature requests will **not** be worked on by the creators at this time. 8 | 9 | Thanks again to all the developers who found this project interesting. Feel free ask questions to learn more about how it works and its internals. Hopefully you will venture off and create your own version of Conclave as well. 10 | 11 | ## Summary 12 | 13 | Conclave is an open-source, real-time, collaborative text editor for the browser built from scratch in JavaScript. 14 | 15 | Intrigued by collaboration tools like Google Docs, we set out to build one from scratch. Conclave uses **Conflict-Free Replicated Data Types** (CRDT) to make sure all users stay in-sync and **WebRTC** to allow users to send messages directly to one another. The result is a private and decentralized way to collaborate on documents. 16 | 17 | For more details on how we designed and built Conclave, read our [case study](https://conclave-team.github.io/conclave-site/). 18 | 19 | # How to Run Locally 20 | 21 | You will need node and npm. First download the dependencies. 22 | 23 | ``` 24 | npm install 25 | ``` 26 | 27 | Next, you will need to build and compile the assets and start the server. You can do that all in an npm command. 28 | 29 | ``` 30 | npm run local 31 | ``` 32 | 33 | We've added a Makefile and Dockerfile to make this easier. I highly recommend using them. 34 | 35 | Simply run: 36 | 37 | ``` 38 | make run-local 39 | ``` 40 | 41 | And you will be good to go. 42 | -------------------------------------------------------------------------------- /lib/remoteCursor.js: -------------------------------------------------------------------------------- 1 | import CSS_COLORS from './cssColors'; 2 | import { generateItemFromHash } from './hashAlgo'; 3 | import { ANIMALS } from './cursorNames'; 4 | 5 | export default class RemoteCursor { 6 | constructor(mde, siteId, position) { 7 | this.mde = mde; 8 | 9 | const color = generateItemFromHash(siteId, CSS_COLORS); 10 | const name = generateItemFromHash(siteId, ANIMALS); 11 | 12 | this.createCursor(color); 13 | this.createFlag(color, name); 14 | 15 | this.cursor.appendChild(this.flag); 16 | this.set(position); 17 | } 18 | 19 | createCursor(color) { 20 | const textHeight = this.mde.codemirror.defaultTextHeight(); 21 | 22 | this.cursor = document.createElement('div'); 23 | this.cursor.classList.add('remote-cursor'); 24 | this.cursor.style.backgroundColor = color; 25 | this.cursor.style.height = textHeight + 'px'; 26 | } 27 | 28 | createFlag(color, name) { 29 | const cursorName = document.createTextNode(name); 30 | 31 | this.flag = document.createElement('span'); 32 | this.flag.classList.add('flag'); 33 | this.flag.style.backgroundColor = color; 34 | this.flag.appendChild(cursorName) 35 | } 36 | 37 | set(position) { 38 | this.detach(); 39 | 40 | const coords = this.mde.codemirror.cursorCoords(position, 'local'); 41 | this.cursor.style.left = (coords.left >= 0 ? coords.left : 0) + 'px'; 42 | this.mde.codemirror.getDoc().setBookmark(position, { widget: this.cursor }); 43 | this.lastPosition = position; 44 | 45 | // Add a zero width-space so line wrapping works (on firefox?) 46 | this.cursor.parentElement.appendChild(document.createTextNode("\u200b")); 47 | } 48 | 49 | detach() { 50 | // Used when updating cursor position. 51 | // If cursor exists on the DOM, remove it. 52 | // DO NOT remove cursor's parent. It contains the zero width-space. 53 | if (this.cursor.parentElement) 54 | this.cursor.remove(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title= title 5 | link(rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css") 6 | link(rel='stylesheet' href='css/style.css') 7 | link(rel='shortcut icon' href='assets/img/favicon.ico' type='image/x-icon') 8 | body 9 | .navbar 10 | a(href='/' target="_blank") 11 | h1.logo Conclave 12 | .nav-items 13 | block nav 14 | p#safari Your browser doesn't support WebRTC yet.

Please use the Chrome or Firefox desktop browsers. 15 | #conclave.hide 16 | .text-wrapper 17 | .editor 18 | .header 19 | block link 20 | button#download(type='button') Save 21 | label#upload(for='file') Upload 22 | input#file(type='file' accept='.txt, .js, .rb, .md') 23 | textarea(row='10' col='20') 24 | p#peerId Peers: 25 | .video-modal.hide 26 | .video-bar 27 | i(data-feather="minus" class="minimize") 28 | i(data-feather='x' class="exit") 29 | video 30 | .loading 31 | p Loading 32 | .sk-fading-circle 33 | .sk-circle1.sk-circle 34 | .sk-circle2.sk-circle 35 | .sk-circle3.sk-circle 36 | .sk-circle4.sk-circle 37 | .sk-circle5.sk-circle 38 | .sk-circle6.sk-circle 39 | .sk-circle7.sk-circle 40 | .sk-circle8.sk-circle 41 | .sk-circle9.sk-circle 42 | .sk-circle10.sk-circle 43 | .sk-circle11.sk-circle 44 | .sk-circle12.sk-circle 45 | p Conclave currently supports Chrome or Firefox desktop browsers. 46 | p Note: Chrome v63 introduced a bug that causes sporadic issues. If loading takes longer than 10 sec, please upgrade to Chrome v64 or consider using Firefox. 47 | script(src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js") 48 | script. 49 | feather.replace({ 'stroke-width': 3 }); 50 | block scripts 51 | -------------------------------------------------------------------------------- /lib/demo.js: -------------------------------------------------------------------------------- 1 | import Peer from 'peerjs'; 2 | import SimpleMDE from 'simplemde'; 3 | 4 | import DemoController from './controller'; 5 | import Broadcast from './broadcast'; 6 | import Editor from './editor'; 7 | import UserBot from './userBot'; 8 | 9 | const id = Math.floor(Math.random() * 100000); 10 | 11 | const demo = new DemoController( 12 | (location.search.slice(1) || '0'), 13 | location.origin, 14 | new Peer('conclave-demo-'+id, { 15 | debug: 3 16 | }), 17 | new Broadcast(), 18 | new Editor(new SimpleMDE({ 19 | placeholder: "Share the link to invite collaborators to your document.", 20 | spellChecker: false, 21 | toolbar: false, 22 | autofocus: true, 23 | indentWithTabs: true, 24 | tabSize: 4, 25 | indentUnit: 4, 26 | lineWrapping: false, 27 | shortCuts: [] 28 | })) 29 | ); 30 | 31 | const script1 = `Conclave is a private and secure real-time collaborative text editor. Conclave 32 | allows you to create and edit documents with multiple people all at the same time. 33 | 34 | ### How Do I Use It? 35 | 36 | To start editing, click the *New Document* link above, and then click the blue 37 | boxes icon to copy the *Sharing Link* to your clipboard. Share the link however 38 | you'd like with your collaborators. 39 | 40 | ### Doesn't Google Already Do This? 41 | 42 | Kind of, but Conclave is decentralized and therefore private. Google stores your 43 | documents on their servers where they and the government could access them. With 44 | Conclave, your document is stored only on your computer and any changes you make 45 | are sent only to the people collaborating with you. Also Google is pretty big. 46 | We're just three engineers who created Conclave in a month. Click *Our Team* above 47 | to learn more about us. 48 | 49 | ### What Else Can Conclave Do? 50 | 51 | - Upload a document from your computer to continue editing 52 | - Save the document to your computer at any time 53 | 54 | Happy Typing!`; 55 | 56 | new UserBot('conclave-bot'+id, 'conclave-demo-'+id, script1, demo.editor.mde); 57 | 58 | -------------------------------------------------------------------------------- /lib/versionVector.js: -------------------------------------------------------------------------------- 1 | import SortedArray from './sortedArray'; 2 | import Version from './version'; 3 | 4 | // vector/list of versions of sites in the distributed system 5 | // keeps track of the latest operation received from each site (i.e. version) 6 | // prevents duplicate operations from being applied to our CRDT 7 | class VersionVector { 8 | // initialize empty vector to be sorted by siteId 9 | // initialize Version/Clock for local site and insert into SortedArray vector object 10 | constructor(siteId) { 11 | // this.versions = new SortedArray(this.siteIdComparator); 12 | this.versions = [] 13 | this.localVersion = new Version(siteId); 14 | this.versions.push(this.localVersion); 15 | } 16 | 17 | increment() { 18 | this.localVersion.counter++; 19 | } 20 | 21 | // updates vector with new version received from another site 22 | // if vector doesn't contain version, it's created and added to vector 23 | // create exceptions if need be. 24 | update(incomingVersion) { 25 | const existingVersion = this.versions.find(version => incomingVersion.siteId === version.siteId); 26 | 27 | if (!existingVersion) { 28 | const newVersion = new Version(incomingVersion.siteId); 29 | 30 | newVersion.update(incomingVersion); 31 | this.versions.push(newVersion); 32 | } else { 33 | existingVersion.update(incomingVersion); 34 | } 35 | } 36 | 37 | // check if incoming remote operation has already been applied to our crdt 38 | hasBeenApplied(incomingVersion) { 39 | const localIncomingVersion = this.getVersionFromVector(incomingVersion); 40 | const isIncomingInVersionVector = !!localIncomingVersion; 41 | 42 | if (!isIncomingInVersionVector) return false; 43 | 44 | const isIncomingLower = incomingVersion.counter <= localIncomingVersion.counter; 45 | const isInExceptions = localIncomingVersion.exceptions.includes(incomingVersion.counter); 46 | 47 | return isIncomingLower && !isInExceptions; 48 | } 49 | 50 | getVersionFromVector(incomingVersion) { 51 | return this.versions.find(version => version.siteId === incomingVersion.siteId); 52 | } 53 | 54 | getLocalVersion() { 55 | return { 56 | siteId: this.localVersion.siteId, 57 | counter: this.localVersion.counter 58 | }; 59 | } 60 | } 61 | 62 | export default VersionVector; 63 | -------------------------------------------------------------------------------- /spec/editorSpec.js: -------------------------------------------------------------------------------- 1 | import { JSDOM } from 'jsdom'; 2 | 3 | import Editor from '../lib/editor'; 4 | 5 | describe("Editor", () => { 6 | const mockMDE = { 7 | codemirror: { 8 | setOption: function() {} 9 | } 10 | }; 11 | const editor = new Editor(mockMDE); 12 | editor.controller = { 13 | crdt: { text: '' } 14 | }; 15 | 16 | describe("constructor", () => { 17 | it("sets the mde passed in to the.mde", () => { 18 | expect(editor.mde).toEqual(mockMDE); 19 | }); 20 | }); 21 | 22 | describe("bindChangeEvent", () => { 23 | // it("is triggered by a change in the codemirror", () => { 24 | // spyOn(editor.mde.codemirror, "on"); 25 | // editor.mde.codemirror.trigger("change"); 26 | // expect(editor.mde.codemirror.on).toHaveBeenCalled(); 27 | // }); 28 | // 29 | // it("changes the character text to a new line", () => { 30 | // 31 | // }); 32 | // 33 | // it("calls controller.handleInsert when change was an insert", () => { 34 | // 35 | // }); 36 | // 37 | // it("calls controller.handleDelete when change was a deletion", () => { 38 | // 39 | // }); 40 | }); 41 | 42 | describe("updateView", () => { 43 | beforeEach(() => { 44 | const dom = new JSDOM(``); 45 | }); 46 | 47 | // it("adds text to the view", () => { 48 | // const editor = new Editor(null); 49 | // const newText = "I am here." 50 | // editor.updateView(newText); 51 | // 52 | // expect(editor.mde.value()).toEqual(newText); 53 | // }); 54 | // 55 | // it("removes text from the view", () => { 56 | // 57 | // }); 58 | // 59 | // it("retains the cursor position", () => { 60 | // 61 | // }); 62 | }); 63 | 64 | describe("findLinearIdx", () => { 65 | it("returns 0 if lines of text is empty", () => { 66 | editor.controller.crdt.text = ""; 67 | expect(editor.findLinearIdx(0, 0)).toEqual(0); 68 | }); 69 | 70 | it("calculates linear index from a single line of text", () => { 71 | editor.controller.crdt.text = "abcdefghijklmnop"; 72 | expect(editor.findLinearIdx(0, 7)).toEqual(7); 73 | }); 74 | 75 | it("calculates linear index from multiple lines of text", () => { 76 | editor.controller.crdt.text = "abc\ndefgh\nijk\nlmnop"; 77 | expect(editor.findLinearIdx(1, 2)).toEqual(6); 78 | }); 79 | 80 | it("can find the linear index on the last line of text", () => { 81 | editor.controller.crdt.text = "abc\ndefgh\nijk\nlmnop"; 82 | expect(editor.findLinearIdx(3, 2)).toEqual(16); 83 | }); 84 | 85 | it("can find the linear index at the end of a line of text", () => { 86 | editor.controller.crdt.text = "abc\ndefgh\nijk\nlmnop"; 87 | expect(editor.findLinearIdx(1, 5)).toEqual(9); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /spec/charSpec.js: -------------------------------------------------------------------------------- 1 | import Char from '../lib/char'; 2 | import Identifier from "../lib/identifier"; 3 | 4 | describe("Char", () => { 5 | describe("compareTo", () => { 6 | let char1; 7 | let id1; 8 | let id2; 9 | let id3; 10 | 11 | beforeEach(() => { 12 | id1 = new Identifier(2, 1); 13 | id2 = new Identifier(5, 1); 14 | id3 = new Identifier(1, 2); 15 | char1 = new Char("a", 0, 2, [id1, id2, id3]); 16 | }); 17 | 18 | it("returns -1 if first position is 'lower' than second position", () => { 19 | const id21 = new Identifier(2, 1); 20 | const id22 = new Identifier(5, 1); 21 | const id23 = new Identifier(3, 2); 22 | const char2 = new Char("b", 0, 2, [id21, id22, id23]); 23 | expect(char1.compareTo(char2)).toEqual(-1); 24 | }); 25 | 26 | it("returns -1 if first site is 'lower' than second site", () => { 27 | const id21 = new Identifier(2, 1); 28 | const id22 = new Identifier(5, 2); 29 | const id23 = new Identifier(1, 2); 30 | const char2 = new Char("b", 0, 2, [id21, id22, id23]); 31 | expect(char1.compareTo(char2)).toEqual(-1); 32 | }); 33 | 34 | it("returns -1 if first position is 'shorter' than second position", () => { 35 | const id21 = new Identifier(2, 1); 36 | const id22 = new Identifier(5, 1); 37 | const id23 = new Identifier(1, 2); 38 | const id24 = new Identifier(8, 2); 39 | const char2 = new Char("b", 0, 2, [id21, id22, id23, id24]); 40 | expect(char1.compareTo(char2)).toEqual(-1); 41 | }); 42 | 43 | it("returns 1 if first position is 'higher' than second position", () => { 44 | const id21 = new Identifier(2, 1); 45 | const id22 = new Identifier(3, 1); 46 | const id23 = new Identifier(1, 2); 47 | const char2 = new Char("b", 0, 2, [id21, id22, id23]); 48 | expect(char1.compareTo(char2)).toEqual(1); 49 | }); 50 | 51 | it("returns 1 if first site is 'higher' than second site", () => { 52 | const id21 = new Identifier(2, 1); 53 | const id22 = new Identifier(5, 1); 54 | const id23 = new Identifier(1, 1); 55 | const char2 = new Char("b", 0, 1, [id21, id22, id23]); 56 | expect(char1.compareTo(char2)).toEqual(1); 57 | }); 58 | 59 | it("returns 1 if first position is 'longer' than second position", () => { 60 | const id21 = new Identifier(2, 1); 61 | const id22 = new Identifier(5, 1); 62 | const char2 = new Char("b", 0, 1, [id21, id22]); 63 | expect(char1.compareTo(char2)).toEqual(1); 64 | }); 65 | 66 | it("returns 0 if positions are exactly the same", () => { 67 | const id21 = new Identifier(2, 1); 68 | const id22 = new Identifier(5, 1); 69 | const id23 = new Identifier(1, 2); 70 | const char2 = new Char("b", 0, 2, [id21, id22, id23]); 71 | expect(char1.compareTo(char2)).toEqual(0); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /lib/idGraph.js: -------------------------------------------------------------------------------- 1 | import CRDT from './crdtLinear'; 2 | import * as Util from './utilLinear'; 3 | import UUID from 'uuid/v1'; 4 | 5 | function mockController() { 6 | return { 7 | siteId: UUID(), 8 | broadcastInsertion: function() {}, 9 | broadcastDeletion: function() {}, 10 | insertIntoEditor: function() {}, 11 | deleteFromEditor: function() {}, 12 | vector: { 13 | localVersion: { 14 | counter: 0 15 | }, 16 | increment: function() { 17 | this.localVersion.counter++; 18 | } 19 | } 20 | } 21 | } 22 | 23 | let multipliers, bases, boundaries, strategies, crdt, xs, ys, data, name, title; 24 | const ops = [100, 500, 1000, 3000, 5000]; 25 | const funcs = [ 26 | [Util.insertRandom, 'Inserted Randomly'], 27 | [Util.insertEnd, 'Inserted at End'], 28 | [Util.insertBeginning, 'Inserted at Beginning'] 29 | ]; 30 | 31 | 32 | // comparing multipliers 33 | 34 | multipliers = [1, 2, 3]; 35 | data = []; 36 | 37 | multipliers.forEach(mult => { 38 | funcs.forEach(func => { 39 | xs = []; 40 | ys = []; 41 | crdt = new CRDT(mockController(), 32, 10, 'random', mult); 42 | crdt.insertText = function() {}; 43 | crdt.deleteText = function() {}; 44 | ops.forEach(op => { 45 | func[0](crdt, op); 46 | xs.push(op); 47 | ys.push(Util.avgIdLength(crdt)); 48 | crdt.struct = []; 49 | }); 50 | name = `multiplier: ${mult}, ${func[1]}`; 51 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 52 | }); 53 | }); 54 | 55 | title = 'Different Base Multiplications (base = 32, boundary = 10, strategy = random)' 56 | Plotly.newPlot('g0', data, {title: title, height: 600}); 57 | 58 | 59 | // comparing base 60 | 61 | bases = [32, 1024, 4096]; 62 | data = []; 63 | 64 | bases.forEach(base => { 65 | funcs.forEach(func => { 66 | xs = []; 67 | ys = []; 68 | crdt = new CRDT(mockController(), base, 10, 'random', 2); 69 | crdt.insertText = function() {}; 70 | crdt.deleteText = function() {}; 71 | ops.forEach(op => { 72 | func[0](crdt, op); 73 | xs.push(op); 74 | ys.push(Util.avgIdLength(crdt)); 75 | crdt.struct = []; 76 | }); 77 | name = `base: ${base}, ${func[1]}`; 78 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 79 | }); 80 | }); 81 | 82 | title = 'Different Starting Bases (mult = 2, boundary = 10, strategy = random)'; 83 | Plotly.newPlot('g1', data, {title: title, height: 600}); 84 | 85 | 86 | // comparing boundary 87 | 88 | boundaries = [10, 20, 30]; 89 | data = []; 90 | 91 | boundaries.forEach(boundary => { 92 | funcs.forEach(func => { 93 | xs = []; 94 | ys = []; 95 | crdt = new CRDT(mockController(), 32, boundary, 'random', 2); 96 | crdt.insertText = function() {}; 97 | crdt.deleteText = function() {}; 98 | ops.forEach(op => { 99 | func[0](crdt, op); 100 | xs.push(op); 101 | ys.push(Util.avgIdLength(crdt)); 102 | crdt.struct = []; 103 | }); 104 | name = `boundary: ${boundary}, ${func[1]}`; 105 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 106 | }); 107 | }); 108 | 109 | title = 'Different Boundaries (mult = 2, base = 32, strategy = random)'; 110 | Plotly.newPlot('g2', data, {title: title, height: 600}); 111 | 112 | 113 | // comparing strategy 114 | 115 | strategies = ['every2nd', 'every3rd', 'random']; 116 | data = []; 117 | 118 | strategies.forEach(strat => { 119 | funcs.forEach(func => { 120 | xs = []; 121 | ys = []; 122 | crdt = new CRDT(mockController(), 32, 10, strat, 2); 123 | crdt.insertText = function() {}; 124 | crdt.deleteText = function() {}; 125 | ops.forEach(op => { 126 | func[0](crdt, op); 127 | xs.push(op); 128 | ys.push(Util.avgIdLength(crdt)); 129 | crdt.struct = []; 130 | }); 131 | name = `strategy: ${strat}, ${func[1]}`; 132 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 133 | }); 134 | }); 135 | 136 | title = 'Different Strategies (mult = 2, base = 32, boundary = 10)'; 137 | Plotly.newPlot('g3', data, {title: title, height: 600}); 138 | -------------------------------------------------------------------------------- /spec/versionVectorSpec.js: -------------------------------------------------------------------------------- 1 | import VersionVector from "../lib/VersionVector"; 2 | 3 | describe("VersionVector", () => { 4 | describe('constructor', () => { 5 | const vector = new VersionVector(10); 6 | 7 | it('initializes a local property on the object', () => { 8 | expect(vector.localVersion).toBeTruthy(); 9 | }); 10 | 11 | it('initializes a Versions property', () => { 12 | expect(vector.versions).toBeTruthy(); 13 | }); 14 | 15 | it('puts local vector in the all vector', () => { 16 | expect(vector.versions[0].siteId).toBe(10); 17 | }); 18 | }); 19 | 20 | describe('increment', () => { 21 | it('increments the counter in the local property', () => { 22 | const vector = new VersionVector(10); 23 | 24 | expect(vector.localVersion.counter).toBe(0); 25 | 26 | vector.increment(); 27 | expect(vector.localVersion.counter).toBe(1); 28 | }); 29 | 30 | it('increments the counter of the version in the Versions array', () => { 31 | const vector = new VersionVector(10); 32 | vector.increment(); 33 | 34 | expect(vector.versions[0].counter).toBe(1) 35 | }); 36 | }); 37 | 38 | describe('update', () => { 39 | it('increments the version if the entry exists in the all arr', () => { 40 | const vector = new VersionVector(10); 41 | vector.update({siteId: 10, counter: 1}); 42 | 43 | expect(vector.versions[0].counter).toBe(1); 44 | }); 45 | 46 | it('creates the entry if it does not exist and then increments', () => { 47 | const vector = new VersionVector(10); 48 | 49 | expect(vector.versions[0].siteId).toBe(10); 50 | vector.update({siteId: 5, counter: 1}); 51 | 52 | expect(vector.versions[1].siteId).toBe(5); 53 | expect(vector.versions[1].counter).toBe(1); 54 | }); 55 | 56 | it('creates exceptions if version counter is greater by more than 1', () => { 57 | const vector = new VersionVector(10); 58 | vector.update({siteId: 10, counter: 2}); 59 | 60 | expect(vector.versions[0].exceptions.includes(1)).toBe(true); 61 | }); 62 | 63 | it('does not update version counter if remote version counter is equal to current counter', () => { 64 | const vector = new VersionVector(10); 65 | vector.update({siteId: 10, counter: 0}); 66 | 67 | expect(vector.versions[0].counter).toBe(0); 68 | }); 69 | 70 | it('does not update version counter if remote version counter is less than current counter', () => { 71 | const vector = new VersionVector(10); 72 | vector.update({siteId: 10, counter: -1}); 73 | expect(vector.versions[0].counter).toBe(0); 74 | }); 75 | 76 | it('removes exceptions if counter exists in exceptions set', () => { 77 | const vector = new VersionVector(10); 78 | vector.update({siteId: 10, counter: 2}); 79 | expect(vector.versions[0].exceptions.includes(1)).toBe(true); 80 | 81 | vector.update({siteId: 10, counter: 1}); 82 | expect(vector.versions[0].exceptions.includes(1)).toBe(false); 83 | }); 84 | }); 85 | 86 | describe('hasBeenApplied', () => { 87 | it('returns true if remote counter is equal to or less than local version counter and no exceptions', () => { 88 | const vector = new VersionVector(10); 89 | vector.update({siteId: 10, counter: 1}); 90 | vector.update({siteId: 10, counter: 2}); 91 | 92 | expect(vector.hasBeenApplied({siteId: 10, counter: 1})).toBe(true); 93 | expect(vector.hasBeenApplied({siteId: 10, counter: 2})).toBe(true); 94 | }); 95 | 96 | it('returns false if version does not exist', () => { 97 | const vector = new VersionVector(10); 98 | 99 | expect(vector.hasBeenApplied({siteId: 5, counter: 1})).toBe(false); 100 | }); 101 | 102 | it('returns false if version counter is greater than stored version', () => { 103 | const vector = new VersionVector(10); 104 | vector.update({siteId: 10, counter: 1}); 105 | 106 | expect(vector.hasBeenApplied({siteId: 10, counter: 2})).toBe(false); 107 | }); 108 | 109 | it('returns false if version counter is in exceptions', () => { 110 | const vector = new VersionVector(10); 111 | vector.update({siteId: 10, counter: 2}); 112 | 113 | expect(vector.hasBeenApplied({siteId: 10, counter: 1})).toBe(false); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /lib/userBot.js: -------------------------------------------------------------------------------- 1 | import CRDT from './crdt'; 2 | import VersionVector from './versionVector'; 3 | import Peer from 'peerjs'; 4 | import Identifier from './identifier'; 5 | import Char from './char'; 6 | 7 | class UserBot { 8 | constructor(peerId, targetPeerId, script, mde) { 9 | this.siteId = 'bot-1'; 10 | this.peer = new Peer(peerId, { 11 | debug: 3 12 | }); 13 | this.vector = new VersionVector(this.siteId); 14 | this.crdt = new CRDT(this); 15 | this.buffer = []; 16 | this.mde = mde; 17 | this.script = script; 18 | 19 | this.peer.on('open', () => { 20 | this.connectToUser(targetPeerId); 21 | this.onConnection(); 22 | this.runScript(75) 23 | }) 24 | } 25 | 26 | connectToUser(targetPeerId) { 27 | this.connection = this.peer.connect(targetPeerId); 28 | 29 | this.connection.on('open', () => { 30 | const message = JSON.stringify({ 31 | type: "add to network", 32 | newPeer: this.peer.id, 33 | newSite: this.siteId 34 | }); 35 | this.connection.send(message); 36 | }); 37 | } 38 | 39 | runScript(interval) { 40 | this.counter = 0; 41 | const self = this; 42 | let line = 0; 43 | let ch = 0; 44 | 45 | self.intervalId = setInterval(function() { 46 | let index = self.counter; 47 | let val = self.script[self.counter++]; 48 | let pos = { line: line, ch: ch }; 49 | ch++; 50 | 51 | if (!val) { 52 | clearInterval(self.intervalId); 53 | return; 54 | } else if (val === '\n') { 55 | line++; 56 | ch = 0; 57 | } 58 | 59 | self.crdt.handleLocalInsert(val, pos); 60 | }, interval); 61 | } 62 | 63 | onConnection() { 64 | this.peer.on('connection', connection => { 65 | connection.on('data', data => { 66 | const dataObj = JSON.parse(data); 67 | 68 | this.handleRemoteOperation(dataObj); 69 | }); 70 | }); 71 | } 72 | 73 | processDeletionBuffer() { 74 | let i = 0; 75 | let deleteOperation; 76 | 77 | while (i < this.buffer.length) { 78 | deleteOperation = this.buffer[i]; 79 | 80 | if (this.hasInsertionBeenApplied(deleteOperation)) { 81 | this.applyOperation(deleteOperation); 82 | this.buffer.splice(i, 1); 83 | } else { 84 | i++; 85 | } 86 | } 87 | } 88 | 89 | hasInsertionBeenApplied(operation) { 90 | const charVersion = { siteId: operation.char.siteId, counter: operation.char.counter }; 91 | return this.vector.hasBeenApplied(charVersion); 92 | } 93 | 94 | handleRemoteOperation(operation) { 95 | if (this.vector.hasBeenApplied(operation.version)) return; 96 | 97 | if (operation.type === 'insert') { 98 | this.applyOperation(operation); 99 | } else if (operation.type === 'delete') { 100 | this.buffer.push(operation); 101 | } 102 | 103 | this.processDeletionBuffer(); 104 | } 105 | 106 | applyOperation(operation) { 107 | const char = operation.char; 108 | const identifiers = char.position.map(pos => new Identifier(pos.digit, pos.siteId)); 109 | const newChar = new Char(char.value, char.counter, char.siteId, identifiers); 110 | 111 | if (operation.type === 'insert') { 112 | this.crdt.handleRemoteInsert(newChar); 113 | } else if (operation.type === 'delete') { 114 | this.crdt.handleRemoteDelete(newChar, operation.version.siteId); 115 | } 116 | 117 | this.vector.update(operation.version); 118 | } 119 | 120 | broadcastInsertion(char) { 121 | const operation = JSON.stringify({ 122 | type: 'insert', 123 | char: char, 124 | version: this.vector.getLocalVersion() 125 | }); 126 | 127 | if (this.connection.open) { 128 | this.connection.send(operation); 129 | } else { 130 | this.connection.on('open', () => { 131 | this.connection.send(operation); 132 | }); 133 | } 134 | } 135 | 136 | broadcastDeletion(char) { 137 | const operation = JSON.stringify({ 138 | type: 'delete', 139 | char: char, 140 | version: this.vector.getLocalVersion() 141 | }); 142 | 143 | if (this.connection.open) { 144 | this.connection.send(operation); 145 | } else { 146 | this.connection.on('open', () => { 147 | this.connection.send(operation); 148 | }); 149 | } 150 | } 151 | 152 | insertIntoEditor() {} 153 | deleteFromEditor() {} 154 | } 155 | 156 | export default UserBot; 157 | -------------------------------------------------------------------------------- /lib/timeGraph.js: -------------------------------------------------------------------------------- 1 | import CRDT from './crdtLinear'; 2 | import * as Util from './utilLinear'; 3 | import UUID from 'uuid/v1'; 4 | 5 | function mockController() { 6 | return { 7 | siteId: UUID(), 8 | broadcastInsertion: function() {}, 9 | broadcastDeletion: function() {}, 10 | insertIntoEditor: function() {}, 11 | deleteFromEditor: function() {}, 12 | vector: { 13 | localVersion: { 14 | counter: 0 15 | }, 16 | increment: function() { 17 | this.localVersion.counter++; 18 | } 19 | } 20 | } 21 | } 22 | 23 | let funcs, crdt, xs, ys, data, name, title; 24 | const bases = [32, 2064]; 25 | const boundaries = [10, 100]; 26 | const ops = [1000, 5000, 10000, 20000]; 27 | 28 | 29 | // local insertions 30 | 31 | funcs = [[Util.insertRandom, 'random'], [Util.insertEnd, 'at end'], [Util.insertBeginning, 'at beginning']]; 32 | data = []; 33 | 34 | funcs.forEach(func => { 35 | bases.forEach(base => { 36 | boundaries.forEach(boundary => { 37 | xs = []; 38 | ys = []; 39 | crdt = new CRDT(mockController(), base, boundary, 'random', 2); 40 | crdt.insertText = function() {}; 41 | crdt.deleteText = function() {}; 42 | ops.forEach(op => { 43 | xs.push(op); 44 | ys.push(func[0](crdt, op)); 45 | crdt.struct = []; 46 | }); 47 | name = `base: ${base}, boundary: ${boundary}, ${func[1]}`; 48 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 49 | }); 50 | }); 51 | }); 52 | 53 | title = 'Local Insertions, Different Bases and Boundaries (mult = 2, strategy = random)'; 54 | Plotly.newPlot('g0', data, {title: title, height: 600}); 55 | 56 | 57 | // local deletions 58 | 59 | funcs = [[Util.deleteRandom, 'random'], [Util.deleteEnd, 'at end'], [Util.deleteEnd, 'at beginning']]; 60 | data = []; 61 | 62 | funcs.forEach(func => { 63 | bases.forEach(base => { 64 | boundaries.forEach(boundary => { 65 | xs = []; 66 | ys = []; 67 | crdt = new CRDT(mockController(), base, boundary, 'random', 2); 68 | crdt.insertText = function() {}; 69 | crdt.deleteText = function() {}; 70 | ops.forEach(op => { 71 | xs.push(op); 72 | Util.insertRandom(crdt, op); 73 | ys.push(func[0](crdt, op)); 74 | crdt.struct = []; 75 | }); 76 | name = `base: ${base}, boundary: ${boundary}, ${func[1]}`; 77 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 78 | }); 79 | }); 80 | }); 81 | 82 | title = 'Local Deletions, Different Bases and Boundaries (mult = 2, strategy = random)'; 83 | Plotly.newPlot('g1', data, {title: title, height: 600}); 84 | 85 | 86 | // remote insertions 87 | 88 | funcs = [[Util.remoteInsertRandom, 'random'], [Util.remoteInsertEnd, 'at end'], [Util.remoteInsertBeginning, 'at beginning']]; 89 | data = []; 90 | 91 | funcs.forEach(func => { 92 | bases.forEach(base => { 93 | boundaries.forEach(boundary => { 94 | xs = []; 95 | ys = []; 96 | crdt = new CRDT(mockController(), base, boundary, 'random', 2); 97 | crdt.insertText = function() {}; 98 | crdt.deleteText = function() {}; 99 | ops.forEach(op => { 100 | xs.push(op); 101 | ys.push(func[0](crdt, op)); 102 | crdt.struct = []; 103 | }); 104 | name = `base: ${base}, boundary: ${boundary}, ${func[1]}`; 105 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 106 | }); 107 | }); 108 | }); 109 | 110 | title = 'Remote Insertions, Different Bases and Boundaries (mult = 2, strategy = random)'; 111 | Plotly.newPlot('g2', data, {title: title, height: 600}); 112 | 113 | 114 | // remote deletions 115 | 116 | funcs = [[Util.remoteDeleteRandom, 'random'], [Util.remoteDeleteEnd, 'at end'], [Util.remoteDeleteBeginning, 'at beginning']]; 117 | data = []; 118 | 119 | funcs.forEach(func => { 120 | bases.forEach(base => { 121 | boundaries.forEach(boundary => { 122 | xs = []; 123 | ys = []; 124 | ops.forEach(op => { 125 | crdt = new CRDT(mockController(), base, boundary, 'random', 2); 126 | crdt.insertText = function() {}; 127 | crdt.deleteText = function() {}; 128 | Util.remoteInsertRandom(crdt, op); 129 | xs.push(op); 130 | ys.push(func[0](crdt, op)); 131 | }); 132 | name = `base: ${base}, boundary: ${boundary}, ${func[1]}`; 133 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 134 | }); 135 | }); 136 | }); 137 | 138 | title = 'Remote Deletions, Different Bases and Boundaries (mult = 2, strategy = random)'; 139 | Plotly.newPlot('g3', data, {title: title, height: 600}); 140 | -------------------------------------------------------------------------------- /lib/cursorNames.js: -------------------------------------------------------------------------------- 1 | const ADJECTIVES = [ 2 | 'anonymous', 3 | 'secret', 4 | 'mysterious', 5 | 'silent', 6 | 'covert', 7 | 'private', 8 | 'secluded', 9 | 'undercover', 10 | 'unknown', 11 | 'undisclosed', 12 | 'mystic', 13 | 'furtive', 14 | 'esoteric', 15 | 'strange', 16 | 'mystical', 17 | 'occult', 18 | 'dark', 19 | 'classified', 20 | 'confidential', 21 | 'sneaky', 22 | 'stealthy', 23 | 'cryptic', 24 | 'discreet', 25 | 'camouflaged', 26 | 'hidden', 27 | 'concealed', 28 | 'cloaked', 29 | 'disguised' 30 | ]; 31 | 32 | const ANIMALS = [ 33 | "Aardvark", 34 | "Albatross", 35 | "Alligator", 36 | "Alpaca", 37 | "Ant", 38 | "Anteater", 39 | "Antelope", 40 | "Ape", 41 | "Armadillo", 42 | "Donkey", 43 | "Baboon", 44 | "Badger", 45 | "Barracuda", 46 | "ConclaveBot", 47 | "Bat", 48 | "Bear", 49 | "Beaver", 50 | "Bee", 51 | "Bison", 52 | "Boar", 53 | "Buffalo", 54 | "Butterfly", 55 | "Camel", 56 | "Capybara", 57 | "Caribou", 58 | "Cassowary", 59 | "Cat", 60 | "Caterpillar", 61 | "Cattle", 62 | "Chamois", 63 | "Cheetah", 64 | "Chicken", 65 | "Chimpanzee", 66 | "Chinchilla", 67 | "Chough", 68 | "Clam", 69 | "Cobra", 70 | "Cockroach", 71 | "Cod", 72 | "Cormorant", 73 | "Coyote", 74 | "Crab", 75 | "Crane", 76 | "Crocodile", 77 | "Crow", 78 | "Curlew", 79 | "Deer", 80 | "Dinosaur", 81 | "Dog", 82 | "Dogfish", 83 | "Dolphin", 84 | "Dotterel", 85 | "Dove", 86 | "Dragonfly", 87 | "Duck", 88 | "Dugong", 89 | "Dunlin", 90 | "Eagle", 91 | "Echidna", 92 | "Eel", 93 | "Eland", 94 | "Elephant", 95 | "Elk", 96 | "Emu", 97 | "Falcon", 98 | "Ferret", 99 | "Finch", 100 | "Fish", 101 | "Flamingo", 102 | "Fly", 103 | "Fox", 104 | "Frog", 105 | "Gaur", 106 | "Gazelle", 107 | "Gerbil", 108 | "Giraffe", 109 | "Gnat", 110 | "Gnu", 111 | "Goat", 112 | "Goldfinch", 113 | "Goldfish", 114 | "Goose", 115 | "Gorilla", 116 | "Goshawk", 117 | "Grasshopper", 118 | "Grouse", 119 | "Guanaco", 120 | "Gull", 121 | "Hamster", 122 | "Hare", 123 | "Hawk", 124 | "Hedgehog", 125 | "Heron", 126 | "Herring", 127 | "Hippopotamus", 128 | "Hornet", 129 | "Horse", 130 | "Human", 131 | "Hummingbird", 132 | "Hyena", 133 | "Ibex", 134 | "Ibis", 135 | "Jackal", 136 | "Jaguar", 137 | "Jay", 138 | "Jellyfish", 139 | "Kangaroo", 140 | "Kingfisher", 141 | "Koala", 142 | "Kookabura", 143 | "Kouprey", 144 | "Kudu", 145 | "Lapwing", 146 | "Lark", 147 | "Lemur", 148 | "Leopard", 149 | "Lion", 150 | "Llama", 151 | "Lobster", 152 | "Locust", 153 | "Loris", 154 | "Louse", 155 | "Lyrebird", 156 | "Magpie", 157 | "Mallard", 158 | "Manatee", 159 | "Mandrill", 160 | "Mantis", 161 | "Marten", 162 | "Meerkat", 163 | "Mink", 164 | "Mole", 165 | "Mongoose", 166 | "Monkey", 167 | "Moose", 168 | "Mosquito", 169 | "Mouse", 170 | "Mule", 171 | "Narwhal", 172 | "Newt", 173 | "Nightingale", 174 | "Octopus", 175 | "Okapi", 176 | "Opossum", 177 | "Oryx", 178 | "Ostrich", 179 | "Otter", 180 | "Owl", 181 | "Oyster", 182 | "Panther", 183 | "Parrot", 184 | "Partridge", 185 | "Peafowl", 186 | "Pelican", 187 | "Penguin", 188 | "Pheasant", 189 | "Pig", 190 | "Pigeon", 191 | "Pony", 192 | "Porcupine", 193 | "Porpoise", 194 | "Quail", 195 | "Quelea", 196 | "Quetzal", 197 | "Rabbit", 198 | "Raccoon", 199 | "Rail", 200 | "Ram", 201 | "Rat", 202 | "Raven", 203 | "Reindeer", 204 | "Rhinoceros", 205 | "Rook", 206 | "Salamander", 207 | "Salmon", 208 | "Sandpiper", 209 | "Sardine", 210 | "Scorpion", 211 | "Seahorse", 212 | "Seal", 213 | "Shark", 214 | "Sheep", 215 | "Shrew", 216 | "Skunk", 217 | "Snail", 218 | "Snake", 219 | "Sparrow", 220 | "Spider", 221 | "Spoonbill", 222 | "Squid", 223 | "Squirrel", 224 | "Starling", 225 | "Stingray", 226 | "Stinkbug", 227 | "Stork", 228 | "Swallow", 229 | "Swan", 230 | "Tapir", 231 | "Tarsier", 232 | "Termite", 233 | "Tiger", 234 | "Toad", 235 | "Trout", 236 | "Turkey", 237 | "Turtle", 238 | "Viper", 239 | "Vulture", 240 | "Wallaby", 241 | "Walrus", 242 | "Wasp", 243 | "Weasel", 244 | "Whale", 245 | "Wildcat", 246 | "Wolf", 247 | "Wolverine", 248 | "Wombat", 249 | "Woodcock", 250 | "Woodpecker", 251 | "Worm", 252 | "Wren", 253 | "Yak", 254 | "Zebra" 255 | ]; 256 | 257 | export { 258 | ANIMALS, 259 | ADJECTIVES 260 | }; 261 | -------------------------------------------------------------------------------- /lib/arraysGraph.js: -------------------------------------------------------------------------------- 1 | import CRDTLinear from './crdtLinear'; 2 | import * as UtilLinear from './utilLinear'; 3 | import CRDT from './crdt'; 4 | import * as Util from './util'; 5 | import UUID from 'uuid/v1'; 6 | 7 | 8 | function mockController() { 9 | return { 10 | siteId: UUID(), 11 | broadcastInsertion: function() {}, 12 | broadcastDeletion: function() {}, 13 | insertIntoEditor: function() {}, 14 | deleteFromEditor: function() {}, 15 | vector: { 16 | getLocalVersion: () => {}, 17 | localVersion: { 18 | counter: 0 19 | }, 20 | increment: function() { 21 | this.localVersion.counter++; 22 | } 23 | } 24 | } 25 | } 26 | 27 | let func, base, boundary, mult, boundaryStrategy, crdt, xs, ys, data, name, title; 28 | const ops = [1000, 10000, 20000, 40000, 60000, 80000, 100000]; 29 | base = 32; 30 | boundary = 10; 31 | mult = 2; 32 | boundaryStrategy = 'random'; 33 | 34 | // Local Insertions 35 | title = 'Local Insertions'; 36 | data = []; 37 | 38 | // LINEAR 39 | func = UtilLinear.insertBeginning; 40 | xs = []; 41 | ys = []; 42 | 43 | crdt = new CRDTLinear(mockController(), base, boundary, boundaryStrategy, mult); 44 | crdt.insertText = function() {}; 45 | crdt.deleteText = function() {}; 46 | ops.forEach(op => { 47 | xs.push(op); 48 | ys.push(func(crdt, op)); 49 | crdt.struct = []; 50 | }); 51 | 52 | name = 'Linear'; 53 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 54 | 55 | // array-of-arrays 56 | func = Util.insertBeginning; 57 | xs = []; 58 | ys = []; 59 | 60 | crdt = new CRDT(mockController(), base, boundary, boundaryStrategy, mult); 61 | ops.forEach(op => { 62 | xs.push(op); 63 | ys.push(func(crdt, op)); 64 | crdt.struct = []; 65 | }); 66 | 67 | name = 'Array-of-Arrays'; 68 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 69 | 70 | Plotly.newPlot('g0', data, {title: title, height: 600}); 71 | 72 | // Local Deletions 73 | title = 'Local Deletions'; 74 | data = []; 75 | 76 | // linear 77 | func = UtilLinear.deleteBeginning; 78 | xs = []; 79 | ys = []; 80 | 81 | crdt = new CRDTLinear(mockController(), base, boundary, boundaryStrategy, mult); 82 | crdt.insertText = function() {}; 83 | crdt.deleteText = function() {}; 84 | ops.forEach(op => { 85 | xs.push(op); 86 | UtilLinear.insertEnd(crdt, op); 87 | ys.push(func(crdt, op)); 88 | crdt.struct = []; 89 | }); 90 | 91 | name = 'Linear'; 92 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 93 | 94 | // array-of-arrays 95 | func = Util.deleteBeginning; 96 | xs = []; 97 | ys = []; 98 | 99 | crdt = new CRDT(mockController(), base, boundary, boundaryStrategy, mult); 100 | ops.forEach(op => { 101 | xs.push(op); 102 | Util.insertRandom(crdt, op); 103 | ys.push(func(crdt, op)); 104 | crdt.struct = []; 105 | }); 106 | 107 | name = 'Array-of-Arrays'; 108 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 109 | 110 | Plotly.newPlot('g1', data, {title: title, height: 600}); 111 | 112 | // Remote Insertions 113 | title = "Remote Insertions"; 114 | data = []; 115 | 116 | // linear 117 | func = UtilLinear.remoteInsertBeginning; 118 | xs = []; 119 | ys = []; 120 | crdt = new CRDTLinear(mockController(), base, boundary, boundaryStrategy, mult); 121 | crdt.insertText = function() {}; 122 | crdt.deleteText = function() {}; 123 | ops.forEach(op => { 124 | xs.push(op); 125 | ys.push(func(crdt, op)); 126 | crdt.struct = []; 127 | }); 128 | 129 | name = 'Linear'; 130 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 131 | 132 | // array-of-arrays 133 | func = Util.insertBeginning; 134 | xs = []; 135 | ys = []; 136 | 137 | crdt = new CRDT(mockController(), base, boundary, boundaryStrategy, mult); 138 | ops.forEach(op => { 139 | xs.push(op); 140 | ys.push(func(crdt, op)); 141 | crdt.struct = []; 142 | }); 143 | 144 | name = 'Array-of-Arrays'; 145 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 146 | 147 | Plotly.newPlot('g2', data, {title: title, height: 600}); 148 | 149 | // Remote Deletions 150 | title = 'Remote Deletions'; 151 | data = []; 152 | 153 | // linear 154 | func = UtilLinear.remoteDeleteBeginning; 155 | xs = []; 156 | ys = []; 157 | crdt = new CRDTLinear(mockController(), base, boundary, boundaryStrategy, mult); 158 | crdt.insertText = function() {}; 159 | crdt.deleteText = function() {}; 160 | ops.forEach(op => { 161 | xs.push(op); 162 | UtilLinear.remoteInsertEnd(crdt, op); 163 | ys.push(func(crdt, op)); 164 | crdt.struct = []; 165 | }); 166 | 167 | name = 'Linear'; 168 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 169 | 170 | // array-of-arrays 171 | func = Util.deleteBeginning; 172 | xs = []; 173 | ys = []; 174 | 175 | crdt = new CRDT(mockController(), base, boundary, boundaryStrategy, mult); 176 | ops.forEach(op => { 177 | xs.push(op); 178 | Util.insertRandom(crdt, op); 179 | ys.push(func(crdt, op)); 180 | crdt.struct = []; 181 | }); 182 | 183 | name = 'Array-of-Arrays'; 184 | data.push({x: xs, y: ys, type: 'scatter', name: name}); 185 | 186 | Plotly.newPlot('g3', data, {title: title, height: 600}); 187 | -------------------------------------------------------------------------------- /lib/utilLinear.js: -------------------------------------------------------------------------------- 1 | import Char from './char'; 2 | import CRDT from './crdtLinear'; 3 | import UUID from 'uuid/v1'; 4 | 5 | function mockController() { 6 | return { 7 | siteId: UUID(), 8 | broadcastInsertion: function() {}, 9 | broadcastDeletion: function() {}, 10 | insertIntoEditor: function() {}, 11 | deleteFromEditor: function() {}, 12 | vector: { 13 | localVersion: { 14 | counter: 0 15 | }, 16 | increment: function() { 17 | this.localVersion.counter++; 18 | } 19 | } 20 | } 21 | } 22 | 23 | function insertRandom(crdt, numberOfOperations) { 24 | const start = Date.now(); 25 | let index; 26 | 27 | for(let i = 0; i < numberOfOperations; i++) { 28 | index = Math.floor(Math.random() * i); 29 | crdt.handleLocalInsert('a', index); 30 | } 31 | 32 | const end = Date.now(); 33 | return end - start; 34 | } 35 | 36 | function remoteInsertRandom(crdt, numberOfOperations) { 37 | const chars = generateChars(numberOfOperations); 38 | const randomChars = shuffle(chars); 39 | 40 | return remoteInsert(crdt, randomChars); 41 | } 42 | 43 | function insertBeginning(crdt, numberOfOperations) { 44 | const start = Date.now(); 45 | 46 | for(let i = 0; i < numberOfOperations; i++) { 47 | crdt.handleLocalInsert('a', 0); 48 | } 49 | 50 | const end = Date.now(); 51 | return end - start; 52 | } 53 | 54 | function insertEnd(crdt, numberOfOperations) { 55 | const start = Date.now(); 56 | 57 | for(let i = 0; i < numberOfOperations; i++) { 58 | crdt.handleLocalInsert('a', i); 59 | } 60 | 61 | const end = Date.now(); 62 | return end - start; 63 | } 64 | 65 | function remoteInsertBeginning(crdt, numberOfOperations) { 66 | const chars = generateChars(numberOfOperations); 67 | const descChars = chars.reverse(); 68 | 69 | return remoteInsert(crdt, descChars); 70 | } 71 | 72 | function remoteInsertEnd(crdt, numberOfOperations) { 73 | const ascChars = generateChars(numberOfOperations); 74 | 75 | return remoteInsert(crdt, ascChars); 76 | } 77 | 78 | function remoteInsert(crdt, chars) { 79 | const start = Date.now(); 80 | 81 | chars.forEach(char => crdt.handleRemoteInsert(char)); 82 | 83 | const end = Date.now(); 84 | return end - start; 85 | } 86 | 87 | function deleteRandom(crdt) { 88 | const start = Date.now(); 89 | let index; 90 | 91 | for(let i = crdt.struct.length - 1; i >= 0; i--) { 92 | index = Math.floor(Math.random() * i); 93 | crdt.handleLocalDelete(index); 94 | } 95 | 96 | const end = Date.now(); 97 | return end - start; 98 | } 99 | 100 | function remoteDeleteRandom(crdt) { 101 | let toDel = []; 102 | crdt.struct.forEach(char => toDel.push(char)); 103 | const randomToDel = shuffle(toDel); 104 | return remoteDelete(crdt, randomToDel); 105 | } 106 | 107 | function deleteBeginning(crdt) { 108 | const start = Date.now(); 109 | 110 | for(let i = crdt.struct.length - 1; i >= 0; i--) { 111 | crdt.handleLocalDelete(0); 112 | } 113 | 114 | const end = Date.now(); 115 | return end - start; 116 | } 117 | 118 | function remoteDeleteBeginning(crdt) { 119 | let toDel = []; 120 | crdt.struct.forEach(char => toDel.push(char)); 121 | return remoteDelete(crdt, toDel); 122 | } 123 | 124 | function deleteEnd(crdt) { 125 | const start = Date.now(); 126 | 127 | for(let i = crdt.struct.length - 1; i >= 0; i--) { 128 | crdt.handleLocalDelete(i); 129 | } 130 | 131 | const end = Date.now(); 132 | return end - start; 133 | } 134 | 135 | function remoteDeleteEnd(crdt) { 136 | let toDel = []; 137 | crdt.struct.forEach(char => toDel.push(char)); 138 | const reverseToDel = toDel.reverse(); 139 | return remoteDelete(crdt, reverseToDel); 140 | } 141 | 142 | function remoteDelete(crdt, chars) { 143 | const start = Date.now(); 144 | 145 | chars.forEach(char => crdt.handleRemoteDelete(char)); 146 | 147 | const end = Date.now(); 148 | return end - start; 149 | } 150 | 151 | function generateChars(numberOfOperations) { 152 | const structs = generateRemoteStructs(numberOfOperations); 153 | const charObjects = []; 154 | for (let i = 0; i < structs[0].length; i++) { 155 | structs.forEach(struct => charObjects.push(struct[i])); 156 | } 157 | return charObjects; 158 | } 159 | 160 | function generateRemoteStructs(numberOfOperations) { 161 | const remoteCRDTs = generateRemoteCRDTs(Math.log10(numberOfOperations)); 162 | 163 | const numOfOps = numberOfOperations / remoteCRDTs.length; 164 | 165 | remoteCRDTs.forEach(crdt => insertRandom(crdt, numOfOps)); 166 | 167 | return remoteCRDTs.map(crdt => crdt.struct); 168 | } 169 | 170 | function generateRemoteCRDTs(num) { 171 | let CRDTs = []; 172 | let crdt; 173 | for (let i = 0; i < num; i++) { 174 | crdt = new CRDT(mockController()); 175 | CRDTs.push(crdt); 176 | } 177 | return CRDTs; 178 | } 179 | 180 | function shuffle(a) { 181 | for (let i = a.length - 1; i > 0; i--) { 182 | const j = Math.floor(Math.random() * (i + 1)); 183 | [a[i], a[j]] = [a[j], a[i]]; 184 | } 185 | return a; 186 | } 187 | 188 | function avgIdLength(crdt) { 189 | const idArray = crdt.struct.map(char => char.position.map(id => id.digit).join('')); 190 | const digitLengthSum = idArray.reduce((acc, id) => { return acc + id.length }, 0); 191 | 192 | return Math.floor(digitLengthSum / idArray.length); 193 | } 194 | 195 | function avgPosLength(crdt) { 196 | const posArray = crdt.struct.map(char => char.position.length); 197 | const posLengthSum = posArray.reduce((acc, len) => { return acc + len }, 0); 198 | 199 | return Math.floor(posLengthSum / posArray.length); 200 | } 201 | 202 | function average(time, operations) { 203 | return time / operations; 204 | } 205 | 206 | export { 207 | avgIdLength, 208 | average, 209 | insertRandom, 210 | remoteInsertRandom, 211 | insertEnd, 212 | remoteInsertEnd, 213 | insertBeginning, 214 | remoteInsertBeginning, 215 | deleteRandom, 216 | remoteDeleteRandom, 217 | deleteEnd, 218 | remoteDeleteEnd, 219 | deleteBeginning, 220 | remoteDeleteBeginning, 221 | }; 222 | -------------------------------------------------------------------------------- /lib/crdtLinear.js: -------------------------------------------------------------------------------- 1 | import Identifier from './identifier'; 2 | import Char from './char'; 3 | 4 | class CRDT { 5 | constructor(controller, base=32, boundary=10, strategy='random', mult=2) { 6 | this.controller = controller; 7 | this.vector = controller.vector; 8 | this.struct = []; 9 | this.siteId = controller.siteId; 10 | this.text = ""; 11 | this.base = base; 12 | this.boundary = boundary; 13 | this.strategy = strategy; 14 | this.strategyCache = []; 15 | this.mult = mult; 16 | } 17 | 18 | handleLocalInsert(val, index) { 19 | this.vector.increment(); 20 | 21 | const char = this.generateChar(val, index); 22 | this.insertChar(index, char); 23 | this.insertText(char.value, index); 24 | 25 | this.controller.broadcastInsertion(char); 26 | } 27 | 28 | handleRemoteInsert(char) { 29 | const index = this.findInsertIndex(char); 30 | 31 | this.insertChar(index, char); 32 | this.insertText(char.value, index); 33 | 34 | this.controller.insertIntoEditor(char.value, index, char.siteId); 35 | } 36 | 37 | insertChar(index, char) { 38 | this.struct.splice(index, 0, char); 39 | } 40 | 41 | handleLocalDelete(idx) { 42 | this.vector.increment(); 43 | 44 | const char = this.struct.splice(idx, 1)[0]; 45 | this.deleteText(idx); 46 | 47 | this.controller.broadcastDeletion(char); 48 | } 49 | 50 | handleRemoteDelete(char, siteId) { 51 | const index = this.findIndexByPosition(char); 52 | this.struct.splice(index, 1); 53 | 54 | this.controller.deleteFromEditor(char.value, index, siteId); 55 | this.deleteText(index); 56 | } 57 | 58 | findInsertIndex(char) { 59 | let left = 0; 60 | let right = this.struct.length - 1; 61 | let mid, compareNum; 62 | 63 | if (this.struct.length === 0 || char.compareTo(this.struct[left]) < 0) { 64 | return left; 65 | } else if (char.compareTo(this.struct[right]) > 0) { 66 | return this.struct.length; 67 | } 68 | 69 | while (left + 1 < right) { 70 | mid = Math.floor(left + (right - left) / 2); 71 | compareNum = char.compareTo(this.struct[mid]); 72 | 73 | if (compareNum === 0) { 74 | return mid; 75 | } else if (compareNum > 0) { 76 | left = mid; 77 | } else { 78 | right = mid; 79 | } 80 | } 81 | 82 | return char.compareTo(this.struct[left]) === 0 ? left : right; 83 | } 84 | 85 | findIndexByPosition(char) { 86 | let left = 0; 87 | let right = this.struct.length - 1; 88 | let mid, compareNum; 89 | 90 | if (this.struct.length === 0) { 91 | throw new Error("Character does not exist in CRDT."); 92 | } 93 | 94 | while (left + 1 < right) { 95 | mid = Math.floor(left + (right - left) / 2); 96 | compareNum = char.compareTo(this.struct[mid]); 97 | 98 | if (compareNum === 0) { 99 | return mid; 100 | } else if (compareNum > 0) { 101 | left = mid; 102 | } else { 103 | right = mid; 104 | } 105 | } 106 | 107 | if (char.compareTo(this.struct[left]) === 0) { 108 | return left; 109 | } else if (char.compareTo(this.struct[right]) === 0) { 110 | return right; 111 | } else { 112 | throw new Error("Character does not exist in CRDT."); 113 | } 114 | } 115 | 116 | generateChar(val, index) { 117 | const posBefore = (this.struct[index - 1] && this.struct[index - 1].position) || []; 118 | const posAfter = (this.struct[index] && this.struct[index].position) || []; 119 | const newPos = this.generatePosBetween(posBefore, posAfter); 120 | const localCounter = this.vector.localVersion.counter; 121 | 122 | return new Char(val, localCounter, this.siteId, newPos); 123 | } 124 | 125 | retrieveStrategy(level) { 126 | if (this.strategyCache[level]) return this.strategyCache[level]; 127 | let strategy; 128 | 129 | switch (this.strategy) { 130 | case 'plus': 131 | strategy = '+'; 132 | break; 133 | case 'minus': 134 | strategy = '-'; 135 | break; 136 | case 'random': 137 | strategy = Math.round(Math.random()) === 0 ? '+' : '-'; 138 | break; 139 | case 'every2nd': 140 | strategy = ((level+1) % 2) === 0 ? '-' : '+'; 141 | break; 142 | case 'every3rd': 143 | strategy = ((level+1) % 3) === 0 ? '-' : '+'; 144 | break; 145 | default: 146 | strategy = ((level+1) % 2) === 0 ? '-' : '+'; 147 | break; 148 | } 149 | 150 | this.strategyCache[level] = strategy; 151 | return strategy; 152 | } 153 | 154 | generatePosBetween(pos1, pos2, newPos=[], level=0) { 155 | let base = Math.pow(this.mult, level) * this.base; 156 | let boundaryStrategy = this.retrieveStrategy(level); 157 | 158 | let id1 = pos1[0] || new Identifier(0, this.siteId); 159 | let id2 = pos2[0] || new Identifier(base, this.siteId); 160 | 161 | if (id2.digit - id1.digit > 1) { 162 | 163 | let newDigit = this.generateIdBetween(id1.digit, id2.digit, boundaryStrategy); 164 | newPos.push(new Identifier(newDigit, this.siteId)); 165 | return newPos; 166 | 167 | } else if (id2.digit - id1.digit === 1) { 168 | 169 | newPos.push(id1); 170 | return this.generatePosBetween(pos1.slice(1), [], newPos, level+1); 171 | 172 | } else if (id1.digit === id2.digit) { 173 | if (id1.siteId < id2.siteId) { 174 | newPos.push(id1); 175 | return this.generatePosBetween(pos1.slice(1), [], newPos, level+1); 176 | } else if (id1.siteId === id2.siteId) { 177 | newPos.push(id1); 178 | return this.generatePosBetween(pos1.slice(1), pos2.slice(1), newPos, level+1); 179 | } else { 180 | throw new Error("Fix Position Sorting"); 181 | } 182 | } 183 | } 184 | /* 185 | Math.random gives you a range that is inclusive of the min and exclusive of the max 186 | so have to add and subtract ones to get them all into that format 187 | 188 | if max - min <= boundary, the boundary doesn't matter 189 | newDigit > min, newDigit < max 190 | ie (min+1...max) 191 | so, min = min + 1 192 | if max - min > boundary and the boundary is negative 193 | min = max - boundary 194 | newDigit >= min, newDigit < max 195 | ie (min...max) 196 | if max - min > boundary and the boundary is positive 197 | max = min + boundary 198 | newDigit > min, newDigit <= max 199 | ie (min+1...max+1) 200 | so, min = min + 1 and max = max + 1 201 | 202 | now all are (min...max) 203 | */ 204 | generateIdBetween(min, max, boundaryStrategy) { 205 | if ((max - min) < this.boundary) { 206 | min = min + 1; 207 | } else { 208 | if (boundaryStrategy === '-') { 209 | min = max - this.boundary; 210 | } else { 211 | min = min + 1; 212 | max = min + this.boundary; 213 | } 214 | } 215 | return Math.floor(Math.random() * (max - min)) + min; 216 | } 217 | 218 | insertText(val, index) { 219 | this.text = this.text.slice(0, index) + val + this.text.slice(index); 220 | } 221 | 222 | deleteText(index) { 223 | this.text = this.text.slice(0, index) + this.text.slice(index + 1); 224 | } 225 | 226 | populateText() { 227 | this.text = this.struct.map(char => char.value).join(''); 228 | } 229 | } 230 | 231 | export default CRDT; 232 | -------------------------------------------------------------------------------- /performance/utilLinear.js: -------------------------------------------------------------------------------- 1 | import Char from '../lib/char'; 2 | import CRDT from '../lib/crdtLinear'; 3 | import { mockController } from './scriptLinear'; 4 | 5 | const CELL_1_SIZE = 17; 6 | const CELL_2_SIZE = 20; 7 | const CELL_3_SIZE = 21; 8 | const CELL_4_SIZE = 16; 9 | const CELL_5_SIZE = 15; 10 | 11 | function insertRandom(crdt, numberOfOperations) { 12 | const start = Date.now(); 13 | let index; 14 | 15 | for(let i = 0; i < numberOfOperations; i++) { 16 | index = Math.floor(Math.random() * i); 17 | crdt.handleLocalInsert('a', index); 18 | } 19 | 20 | const end = Date.now(); 21 | return end - start; 22 | } 23 | 24 | function remoteInsertRandom(crdt, numberOfOperations) { 25 | const chars = generateChars(numberOfOperations); 26 | const randomChars = shuffle(chars); 27 | 28 | return remoteInsert(crdt, randomChars); 29 | } 30 | 31 | function insertBeginning(crdt, numberOfOperations) { 32 | const start = Date.now(); 33 | 34 | for(let i = 0; i < numberOfOperations; i++) { 35 | crdt.handleLocalInsert('a', 0); 36 | } 37 | 38 | const end = Date.now(); 39 | return end - start; 40 | } 41 | 42 | function insertEnd(crdt, numberOfOperations) { 43 | const start = Date.now(); 44 | 45 | for(let i = 0; i < numberOfOperations; i++) { 46 | crdt.handleLocalInsert('a', i); 47 | } 48 | 49 | const end = Date.now(); 50 | return end - start; 51 | } 52 | 53 | function remoteInsertBeginning(crdt, numberOfOperations) { 54 | const chars = generateChars(numberOfOperations); 55 | const descChars = chars.reverse(); 56 | 57 | return remoteInsert(crdt, descChars); 58 | } 59 | 60 | function remoteInsertEnd(crdt, numberOfOperations) { 61 | const ascChars = generateChars(numberOfOperations); 62 | 63 | return remoteInsert(crdt, ascChars); 64 | } 65 | 66 | function remoteInsert(crdt, chars) { 67 | const start = Date.now(); 68 | 69 | chars.forEach(char => crdt.handleRemoteInsert(char)); 70 | 71 | const end = Date.now(); 72 | return end - start; 73 | } 74 | 75 | function deleteRandom(crdt) { 76 | const start = Date.now(); 77 | let index; 78 | 79 | for(let i = crdt.struct.length - 1; i >= 0; i--) { 80 | index = Math.floor(Math.random() * i); 81 | crdt.handleLocalDelete(index); 82 | } 83 | 84 | const end = Date.now(); 85 | return end - start; 86 | } 87 | 88 | function remoteDeleteRandom(crdt) { 89 | let toDel = []; 90 | crdt.struct.forEach(char => toDel.push(char)); 91 | const randomToDel = shuffle(toDel); 92 | return remoteDelete(crdt, randomToDel); 93 | } 94 | 95 | function deleteBeginning(crdt) { 96 | const start = Date.now(); 97 | 98 | for(let i = crdt.struct.length - 1; i >= 0; i--) { 99 | crdt.handleLocalDelete(0); 100 | } 101 | 102 | const end = Date.now(); 103 | return end - start; 104 | } 105 | 106 | function remoteDeleteBeginning(crdt) { 107 | let toDel = []; 108 | crdt.struct.forEach(char => toDel.push(char)); 109 | return remoteDelete(crdt, toDel); 110 | } 111 | 112 | function deleteEnd(crdt) { 113 | const start = Date.now(); 114 | 115 | for(let i = crdt.struct.length - 1; i >= 0; i--) { 116 | crdt.handleLocalDelete(i); 117 | } 118 | 119 | const end = Date.now(); 120 | return end - start; 121 | } 122 | 123 | function remoteDeleteEnd(crdt) { 124 | let toDel = []; 125 | crdt.struct.forEach(char => toDel.push(char)); 126 | const reverseToDel = toDel.reverse(); 127 | return remoteDelete(crdt, reverseToDel); 128 | } 129 | 130 | function remoteDelete(crdt, chars) { 131 | const start = Date.now(); 132 | 133 | chars.forEach(char => crdt.handleRemoteDelete(char)); 134 | 135 | const end = Date.now(); 136 | return end - start; 137 | } 138 | 139 | function generateChars(numberOfOperations) { 140 | const structs = generateRemoteStructs(numberOfOperations); 141 | const charObjects = []; 142 | for (let i = 0; i < structs[0].length; i++) { 143 | structs.forEach(struct => charObjects.push(struct[i])); 144 | } 145 | return charObjects; 146 | } 147 | 148 | function generateRemoteStructs(numberOfOperations) { 149 | const remoteCRDTs = generateRemoteCRDTs(Math.log10(numberOfOperations)); 150 | 151 | const numOfOps = numberOfOperations / remoteCRDTs.length; 152 | 153 | remoteCRDTs.forEach(crdt => insertRandom(crdt, numOfOps)); 154 | 155 | return remoteCRDTs.map(crdt => crdt.struct); 156 | } 157 | 158 | function generateRemoteCRDTs(num) { 159 | let CRDTs = []; 160 | let crdt; 161 | for (let i = 0; i < num; i++) { 162 | crdt = new CRDT(mockController()); 163 | CRDTs.push(crdt); 164 | } 165 | return CRDTs; 166 | } 167 | 168 | function shuffle(a) { 169 | for (let i = a.length - 1; i > 0; i--) { 170 | const j = Math.floor(Math.random() * (i + 1)); 171 | [a[i], a[j]] = [a[j], a[i]]; 172 | } 173 | return a; 174 | } 175 | 176 | function avgIdLength(crdt) { 177 | const idArray = crdt.struct.map(char => char.position.map(id => id.digit).join('')); 178 | const digitLengthSum = idArray.reduce((acc, id) => { return acc + id.length }, 0); 179 | 180 | return Math.floor(digitLengthSum / idArray.length); 181 | } 182 | 183 | function avgPosLength(crdt) { 184 | const posArray = crdt.struct.map(char => char.position.length); 185 | const posLengthSum = posArray.reduce((acc, len) => { return acc + len }, 0); 186 | 187 | return Math.floor(posLengthSum / posArray.length); 188 | } 189 | 190 | function average(time, operations) { 191 | return time / operations; 192 | } 193 | 194 | function addPadding(value, cellSize) { 195 | value = String(value); 196 | 197 | if (value.length > cellSize) { 198 | value = value.slice(0, cellSize); 199 | } 200 | 201 | const padding = ((cellSize - value.length) / 2); 202 | return (' ').repeat(Math.floor(padding)) + value + (' ').repeat(Math.ceil(padding)); 203 | } 204 | 205 | function addRowWithId(operations, crdt, func) { 206 | const totalTime = func(crdt, operations); 207 | const cell1 = addPadding(operations, CELL_1_SIZE); 208 | const cell2 = addPadding(totalTime, CELL_2_SIZE); 209 | const cell3 = addPadding(average(totalTime, operations), CELL_3_SIZE); 210 | const cell4 = addPadding(avgIdLength(crdt), CELL_4_SIZE); 211 | const cell5 = addPadding(avgPosLength(crdt), CELL_5_SIZE); 212 | 213 | return `|${cell1}|${cell2}|${cell3}|${cell4}|${cell5}| 214 | ${'-'.repeat(95)}` 215 | 216 | } 217 | 218 | function addRow(operations, crdt, func) { 219 | const totalTime = func(crdt, operations); 220 | const cell1 = addPadding(operations, CELL_1_SIZE); 221 | const cell2 = addPadding(totalTime, CELL_2_SIZE); 222 | const cell3 = addPadding(average(totalTime, operations), CELL_3_SIZE); 223 | 224 | return `|${cell1}|${cell2}|${cell3}| 225 | ${'-'.repeat(62)}` 226 | } 227 | 228 | function getTimestamp() { 229 | const now = new Date(); 230 | const year = now.getUTCFullYear(); 231 | const month = now.getUTCMonth() + 1; 232 | const date = now.getUTCDate(); 233 | const hours = now.getUTCHours(); 234 | const minutes = now.getUTCMinutes(); 235 | const seconds = now.getUTCSeconds(); 236 | 237 | return `${year}-${month}-${date} ${hours}:${minutes}:${seconds}`; 238 | } 239 | 240 | export { 241 | addRow, 242 | addRowWithId, 243 | insertRandom, 244 | remoteInsertRandom, 245 | insertEnd, 246 | remoteInsertEnd, 247 | insertBeginning, 248 | remoteInsertBeginning, 249 | deleteRandom, 250 | remoteDeleteRandom, 251 | deleteEnd, 252 | remoteDeleteEnd, 253 | deleteBeginning, 254 | remoteDeleteBeginning, 255 | getTimestamp 256 | }; 257 | -------------------------------------------------------------------------------- /performance/comparisons/linearArray/tripleBase/2017-11-17 5:41:7.log: -------------------------------------------------------------------------------- 1 | 2 | #### PERFORMANCE METRICS 3 | Base: 32 | Boundary: 10 | Strategy: minus 4 | ================================================================================================ 5 | 6 | ## RANDOM 7 | --------- 8 | 9 | # LOCAL INSERTIONS 10 | ----------------------------------------------------------------------------------------------- 11 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 12 | | | (in milliseconds) | (in milliseconds) | | Length | 13 | ----------------------------------------------------------------------------------------------- 14 | 15 | | 10 | 1 | 0.1 | 1 | 1 | 16 | ----------------------------------------------------------------------------------------------- 17 | | 100 | 1 | 0.01 | 5 | 2 | 18 | ----------------------------------------------------------------------------------------------- 19 | 20 | # LOCAL DELETIONS 21 | -------------------------------------------------------------- 22 | | # of Operations | Total Execute Time | Avg. Operation Time | 23 | | | (in milliseconds) | (in milliseconds) | 24 | -------------------------------------------------------------- 25 | | 10 | 0 | 0 | 26 | -------------------------------------------------------------- 27 | | 100 | 0 | 0 | 28 | -------------------------------------------------------------- 29 | 30 | # REMOTE INSERTIONS 31 | -------------------------------------------------------------- 32 | | # of Operations | Total Execute Time | Avg. Operation Time | 33 | | | (in milliseconds) | (in milliseconds) | 34 | -------------------------------------------------------------- 35 | | 10 | 0 | 0 | 36 | -------------------------------------------------------------- 37 | | 100 | 1 | 0.01 | 38 | -------------------------------------------------------------- 39 | 40 | # REMOTE DELETIONS 41 | -------------------------------------------------------------- 42 | | # of Operations | Total Execute Time | Avg. Operation Time | 43 | | | (in milliseconds) | (in milliseconds) | 44 | -------------------------------------------------------------- 45 | | 10 | 0 | 0 | 46 | -------------------------------------------------------------- 47 | | 100 | 1 | 0.01 | 48 | -------------------------------------------------------------- 49 | 50 | 51 | ## AT THE BEGINNING 52 | ------------------- 53 | 54 | # LOCAL INSERTIONS 55 | ----------------------------------------------------------------------------------------------- 56 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 57 | | | (in milliseconds) | (in milliseconds) | | Length | 58 | ----------------------------------------------------------------------------------------------- 59 | | 10 | 0 | 0 | 2 | 1 | 60 | ----------------------------------------------------------------------------------------------- 61 | | 100 | 0 | 0 | 4 | 2 | 62 | ----------------------------------------------------------------------------------------------- 63 | 64 | # LOCAL DELETIONS 65 | -------------------------------------------------------------- 66 | | # of Operations | Total Execute Time | Avg. Operation Time | 67 | | | (in milliseconds) | (in milliseconds) | 68 | -------------------------------------------------------------- 69 | | 10 | 0 | 0 | 70 | -------------------------------------------------------------- 71 | | 100 | 0 | 0 | 72 | -------------------------------------------------------------- 73 | 74 | # REMOTE INSERTIONS 75 | -------------------------------------------------------------- 76 | | # of Operations | Total Execute Time | Avg. Operation Time | 77 | | | (in milliseconds) | (in milliseconds) | 78 | -------------------------------------------------------------- 79 | | 10 | 0 | 0 | 80 | -------------------------------------------------------------- 81 | | 100 | 1 | 0.01 | 82 | -------------------------------------------------------------- 83 | 84 | # REMOTE DELETIONS 85 | -------------------------------------------------------------- 86 | | # of Operations | Total Execute Time | Avg. Operation Time | 87 | | | (in milliseconds) | (in milliseconds) | 88 | -------------------------------------------------------------- 89 | | 10 | 0 | 0 | 90 | -------------------------------------------------------------- 91 | | 100 | 0 | 0 | 92 | -------------------------------------------------------------- 93 | 94 | 95 | ## AT THE END 96 | ------------- 97 | 98 | # LOCAL INSERTIONS 99 | ----------------------------------------------------------------------------------------------- 100 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 101 | | | (in milliseconds) | (in milliseconds) | | Length | 102 | ----------------------------------------------------------------------------------------------- 103 | | 10 | 0 | 0 | 4 | 2 | 104 | ----------------------------------------------------------------------------------------------- 105 | | 100 | 2 | 0.02 | 142 | 18 | 106 | ----------------------------------------------------------------------------------------------- 107 | 108 | # LOCAL DELETIONS 109 | -------------------------------------------------------------- 110 | | # of Operations | Total Execute Time | Avg. Operation Time | 111 | | | (in milliseconds) | (in milliseconds) | 112 | -------------------------------------------------------------- 113 | | 10 | 0 | 0 | 114 | -------------------------------------------------------------- 115 | | 100 | 0 | 0 | 116 | -------------------------------------------------------------- 117 | 118 | # REMOTE INSERTIONS 119 | -------------------------------------------------------------- 120 | | # of Operations | Total Execute Time | Avg. Operation Time | 121 | | | (in milliseconds) | (in milliseconds) | 122 | -------------------------------------------------------------- 123 | | 10 | 0 | 0 | 124 | -------------------------------------------------------------- 125 | | 100 | 0 | 0 | 126 | -------------------------------------------------------------- 127 | 128 | # REMOTE DELETIONS 129 | -------------------------------------------------------------- 130 | | # of Operations | Total Execute Time | Avg. Operation Time | 131 | | | (in milliseconds) | (in milliseconds) | 132 | -------------------------------------------------------------- 133 | | 10 | 0 | 0 | 134 | -------------------------------------------------------------- 135 | | 100 | 1 | 0.01 | 136 | -------------------------------------------------------------- 137 | -------------------------------------------------------------------------------- /performance/comparisons/linearArray/tripleBase/2017-11-17 5:40:56.log: -------------------------------------------------------------------------------- 1 | 2 | #### PERFORMANCE METRICS 3 | Base: 3200 | Boundary: 3000 | Strategy: minus 4 | ================================================================================================ 5 | 6 | ## RANDOM 7 | --------- 8 | 9 | # LOCAL INSERTIONS 10 | ----------------------------------------------------------------------------------------------- 11 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 12 | | | (in milliseconds) | (in milliseconds) | | Length | 13 | ----------------------------------------------------------------------------------------------- 14 | 15 | | 10 | 1 | 0.1 | 3 | 1 | 16 | ----------------------------------------------------------------------------------------------- 17 | | 100 | 1 | 0.01 | 4 | 1 | 18 | ----------------------------------------------------------------------------------------------- 19 | 20 | # LOCAL DELETIONS 21 | -------------------------------------------------------------- 22 | | # of Operations | Total Execute Time | Avg. Operation Time | 23 | | | (in milliseconds) | (in milliseconds) | 24 | -------------------------------------------------------------- 25 | | 10 | 0 | 0 | 26 | -------------------------------------------------------------- 27 | | 100 | 0 | 0 | 28 | -------------------------------------------------------------- 29 | 30 | # REMOTE INSERTIONS 31 | -------------------------------------------------------------- 32 | | # of Operations | Total Execute Time | Avg. Operation Time | 33 | | | (in milliseconds) | (in milliseconds) | 34 | -------------------------------------------------------------- 35 | | 10 | 0 | 0 | 36 | -------------------------------------------------------------- 37 | | 100 | 0 | 0 | 38 | -------------------------------------------------------------- 39 | 40 | # REMOTE DELETIONS 41 | -------------------------------------------------------------- 42 | | # of Operations | Total Execute Time | Avg. Operation Time | 43 | | | (in milliseconds) | (in milliseconds) | 44 | -------------------------------------------------------------- 45 | | 10 | 0 | 0 | 46 | -------------------------------------------------------------- 47 | | 100 | 0 | 0 | 48 | -------------------------------------------------------------- 49 | 50 | 51 | ## AT THE BEGINNING 52 | ------------------- 53 | 54 | # LOCAL INSERTIONS 55 | ----------------------------------------------------------------------------------------------- 56 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 57 | | | (in milliseconds) | (in milliseconds) | | Length | 58 | ----------------------------------------------------------------------------------------------- 59 | | 10 | 0 | 0 | 3 | 1 | 60 | ----------------------------------------------------------------------------------------------- 61 | | 100 | 3 | 0.03 | 6 | 3 | 62 | ----------------------------------------------------------------------------------------------- 63 | 64 | # LOCAL DELETIONS 65 | -------------------------------------------------------------- 66 | | # of Operations | Total Execute Time | Avg. Operation Time | 67 | | | (in milliseconds) | (in milliseconds) | 68 | -------------------------------------------------------------- 69 | | 10 | 0 | 0 | 70 | -------------------------------------------------------------- 71 | | 100 | 0 | 0 | 72 | -------------------------------------------------------------- 73 | 74 | # REMOTE INSERTIONS 75 | -------------------------------------------------------------- 76 | | # of Operations | Total Execute Time | Avg. Operation Time | 77 | | | (in milliseconds) | (in milliseconds) | 78 | -------------------------------------------------------------- 79 | | 10 | 0 | 0 | 80 | -------------------------------------------------------------- 81 | | 100 | 0 | 0 | 82 | -------------------------------------------------------------- 83 | 84 | # REMOTE DELETIONS 85 | -------------------------------------------------------------- 86 | | # of Operations | Total Execute Time | Avg. Operation Time | 87 | | | (in milliseconds) | (in milliseconds) | 88 | -------------------------------------------------------------- 89 | | 10 | 0 | 0 | 90 | -------------------------------------------------------------- 91 | | 100 | 1 | 0.01 | 92 | -------------------------------------------------------------- 93 | 94 | 95 | ## AT THE END 96 | ------------- 97 | 98 | # LOCAL INSERTIONS 99 | ----------------------------------------------------------------------------------------------- 100 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 101 | | | (in milliseconds) | (in milliseconds) | | Length | 102 | ----------------------------------------------------------------------------------------------- 103 | | 10 | 0 | 0 | 5 | 1 | 104 | ----------------------------------------------------------------------------------------------- 105 | | 100 | 1 | 0.01 | 36 | 6 | 106 | ----------------------------------------------------------------------------------------------- 107 | 108 | # LOCAL DELETIONS 109 | -------------------------------------------------------------- 110 | | # of Operations | Total Execute Time | Avg. Operation Time | 111 | | | (in milliseconds) | (in milliseconds) | 112 | -------------------------------------------------------------- 113 | | 10 | 0 | 0 | 114 | -------------------------------------------------------------- 115 | | 100 | 0 | 0 | 116 | -------------------------------------------------------------- 117 | 118 | # REMOTE INSERTIONS 119 | -------------------------------------------------------------- 120 | | # of Operations | Total Execute Time | Avg. Operation Time | 121 | | | (in milliseconds) | (in milliseconds) | 122 | -------------------------------------------------------------- 123 | | 10 | 0 | 0 | 124 | -------------------------------------------------------------- 125 | | 100 | 0 | 0 | 126 | -------------------------------------------------------------- 127 | 128 | # REMOTE DELETIONS 129 | -------------------------------------------------------------- 130 | | # of Operations | Total Execute Time | Avg. Operation Time | 131 | | | (in milliseconds) | (in milliseconds) | 132 | -------------------------------------------------------------- 133 | | 10 | 0 | 0 | 134 | -------------------------------------------------------------- 135 | | 100 | 1 | 0.01 | 136 | -------------------------------------------------------------- 137 | -------------------------------------------------------------------------------- /lib/cssColors.js: -------------------------------------------------------------------------------- 1 | // export default [ 2 | // 'rgba(255,127,80,0.5)', 3 | // 'rgba(255,140,0,0.5)', 4 | // 'rgba(255,215,0,0.5)', 5 | // 'rgba(255,165,0,0.5)', 6 | // 'rgba(255,69,0,0.5)', 7 | // 'rgba(255,99,71,0.5)', 8 | // 'rgba(255,20,147,0.5)', 9 | // 'rgba(255,105,180,0.5)', 10 | // 'rgba(255,182,193,0.5)', 11 | // 'rgba(199,21,133,0.5)', 12 | // 'rgba(255,192,203,0.5)', 13 | // 'rgba(219,112,147,0.5)', 14 | // 'rgba(255,255,0,0.5)', 15 | // 'rgba(244,164,96,0.5)', 16 | // 'rgba(160,82,45,0.5)', 17 | // 'rgba(210,180,140,0.5)', 18 | // 'rgba(165,42,42,0.5)', 19 | // 'rgba(222,184,135,0.5)', 20 | // 'rgba(210,105,30,0.5)', 21 | // 'rgba(184,134,11,0.5)', 22 | // 'rgba(128,0,0,0.5)', 23 | // 'rgba(188,143,143,0.5)', 24 | // 'rgba(127,255,0,0.5)', 25 | // 'rgba(0,100,0,0.5)', 26 | // 'rgba(85,107,47,0.5)', 27 | // 'rgba(143,188,143,0.5)', 28 | // 'rgba(34,139,34,0.5)', 29 | // 'rgba(0,128,0,0.5)', 30 | // 'rgba(173,255,47,0.5)', 31 | // 'rgba(124,252,0,0.5)', 32 | // 'rgba(144,238,144,0.5)', 33 | // 'rgba(0,255,0,0.5)', 34 | // 'rgba(50,205,50,0.5)', 35 | // 'rgba(60,179,113,0.5)', 36 | // 'rgba(0,250,154,0.5)', 37 | // 'rgba(128,128,0,0.5)', 38 | // 'rgba(107,142,35,0.5)', 39 | // 'rgba(95,158,160,0.5)', 40 | // 'rgba(0,255,255,0.5)', 41 | // 'rgba(0,139,139,0.5)', 42 | // 'rgba(0,0,255,0.5)', 43 | // 'rgba(138,43,226,0.5)', 44 | // 'rgba(75,0,130,0.5)', 45 | // 'rgba(72,61,139,0.5)', 46 | // 'rgba(65,105,225,0.5)' 47 | // ]; 48 | 49 | export default [ 50 | 'rgba(255, 0, 255, 0.5)', 51 | 'rgba(255, 51, 255, 0.5)', 52 | 'rgba(204, 0, 204, 0.5)', 53 | 'rgba(255, 102, 255, 0.5)', 54 | 'rgba(204, 51, 204, 0.5)', 55 | 'rgba(153, 0, 153, 0.5)', 56 | 'rgba(255, 153, 255, 0.5)', 57 | 'rgba(204, 102, 204, 0.5)', 58 | 'rgba(153, 51, 153, 0.5)', 59 | 'rgba(102, 0, 102, 0.5)', 60 | 'rgba(255, 204, 255, 0.5)', 61 | 'rgba(204, 153, 204, 0.5)', 62 | 'rgba(153, 102, 153, 0.5)', 63 | 'rgba(17, 117, 232, 0.5)', 64 | 'rgba(102, 51, 102, 0.5)', 65 | 'rgba(51, 0, 51, 0.5)', 66 | 'rgba(204, 0, 255, 0.5)', 67 | 'rgba(204, 51, 255, 0.5)', 68 | 'rgba(153, 0, 204, 0.5)', 69 | 'rgba(204, 102, 255, 0.5)', 70 | 'rgba(153, 51, 204, 0.5)', 71 | 'rgba(102, 0, 153, 0.5)', 72 | 'rgba(204, 153, 255, 0.5)', 73 | 'rgba(153, 102, 204, 0.5)', 74 | 'rgba(102, 51, 153, 0.5)', 75 | 'rgba(51, 0, 102, 0.5)', 76 | 'rgba(153, 0, 255, 0.5)', 77 | 'rgba(153, 51, 255, 0.5)', 78 | 'rgba(102, 0, 204, 0.5)', 79 | 'rgba(153, 102, 255, 0.5)', 80 | 'rgba(102, 51, 204, 0.5)', 81 | 'rgba(51, 0, 153, 0.5)', 82 | 'rgba(102, 0, 255, 0.5)', 83 | 'rgba(102, 51, 255, 0.5)', 84 | 'rgba(51, 0, 204, 0.5)', 85 | 'rgba(51, 0, 255, 0.5)', 86 | 'rgba(0, 0, 255, 0.5)', 87 | 'rgba(51, 51, 255, 0.5)', 88 | 'rgba(0, 0, 204, 0.5)', 89 | 'rgba(102, 102, 255, 0.5)', 90 | 'rgba(51, 51, 204, 0.5)', 91 | 'rgba(0, 0, 153, 0.5)', 92 | 'rgba(153, 153, 255, 0.5)', 93 | 'rgba(102, 102, 204, 0.5)', 94 | 'rgba(51, 51, 153, 0.5)', 95 | 'rgba(0, 0, 102, 0.5)', 96 | 'rgba(204, 204, 255, 0.5)', 97 | 'rgba(153, 153, 204, 0.5)', 98 | 'rgba(102, 102, 153, 0.5)', 99 | 'rgba(51, 51, 102, 0.5)', 100 | 'rgba(0, 0, 51, 0.5)', 101 | 'rgba(0, 51, 255, 0.5)', 102 | 'rgba(51, 102, 255, 0.5)', 103 | 'rgba(0, 51, 204, 0.5)', 104 | 'rgba(0, 102, 255, 0.5)', 105 | 'rgba(102, 153, 255, 0.5)', 106 | 'rgba(51, 102, 204, 0.5)', 107 | 'rgba(0, 51, 153, 0.5)', 108 | 'rgba(51, 153, 255, 0.5)', 109 | 'rgba(0, 102, 204, 0.5)', 110 | 'rgba(0, 153, 255, 0.5)', 111 | 'rgba(153, 204, 255, 0.5)', 112 | 'rgba(102, 153, 204, 0.5)', 113 | 'rgba(51, 102, 153, 0.5)', 114 | 'rgba(0, 51, 102, 0.5)', 115 | 'rgba(102, 204, 255, 0.5)', 116 | 'rgba(51, 153, 204, 0.5)', 117 | 'rgba(0, 102, 153, 0.5)', 118 | 'rgba(51, 204, 255, 0.5)', 119 | 'rgba(0, 153, 204, 0.5)', 120 | 'rgba(0, 204, 255, 0.5)', 121 | 'rgba(0, 255, 255, 0.5)', 122 | 'rgba(51, 255, 255, 0.5)', 123 | 'rgba(0, 204, 204, 0.5)', 124 | 'rgba(102, 255, 255, 0.5)', 125 | 'rgba(51, 204, 204, 0.5)', 126 | 'rgba(0, 153, 153, 0.5)', 127 | 'rgba(153, 255, 255, 0.5)', 128 | 'rgba(102, 204, 204, 0.5)', 129 | 'rgba(51, 153, 153, 0.5)', 130 | 'rgba(0, 102, 102, 0.5)', 131 | 'rgba(204, 255, 255, 0.5)', 132 | 'rgba(153, 204, 204, 0.5)', 133 | 'rgba(102, 153, 153, 0.5)', 134 | 'rgba(51, 102, 102, 0.5)', 135 | 'rgba(0, 51, 51, 0.5)', 136 | 'rgba(0, 255, 204, 0.5)', 137 | 'rgba(51, 255, 204, 0.5)', 138 | 'rgba(0, 204, 153, 0.5)', 139 | 'rgba(102, 255, 204, 0.5)', 140 | 'rgba(51, 204, 153, 0.5)', 141 | 'rgba(0, 153, 102, 0.5)', 142 | 'rgba(153, 255, 204, 0.5)', 143 | 'rgba(102, 204, 153, 0.5)', 144 | 'rgba(51, 153, 102, 0.5)', 145 | 'rgba(0, 102, 51, 0.5)', 146 | 'rgba(0, 255, 153, 0.5)', 147 | 'rgba(51, 255, 153, 0.5)', 148 | 'rgba(0, 204, 102, 0.5)', 149 | 'rgba(102, 255, 153, 0.5)', 150 | 'rgba(51, 204, 102, 0.5)', 151 | 'rgba(0, 153, 51, 0.5)', 152 | 'rgba(0, 255, 102, 0.5)', 153 | 'rgba(51, 255, 102, 0.5)', 154 | 'rgba(0, 204, 51, 0.5)', 155 | 'rgba(0, 255, 51, 0.5)', 156 | 'rgba(0, 255, 0, 0.5)', 157 | 'rgba(51, 255, 51, 0.5)', 158 | 'rgba(0, 204, 0, 0.5)', 159 | 'rgba(102, 255, 102, 0.5)', 160 | 'rgba(51, 204, 51, 0.5)', 161 | 'rgba(0, 153, 0, 0.5)', 162 | 'rgba(153, 255, 153, 0.5)', 163 | 'rgba(102, 204, 102, 0.5)', 164 | 'rgba(51, 153, 51, 0.5)', 165 | 'rgba(0, 102, 0, 0.5)', 166 | 'rgba(204, 255, 204, 0.5)', 167 | 'rgba(153, 204, 153, 0.5)', 168 | 'rgba(102, 153, 102, 0.5)', 169 | 'rgba(51, 102, 51, 0.5)', 170 | 'rgba(0, 51, 0, 0.5)', 171 | 'rgba(51, 255, 0, 0.5)', 172 | 'rgba(102, 255, 51, 0.5)', 173 | 'rgba(51, 204, 0, 0.5)', 174 | 'rgba(102, 255, 0, 0.5)', 175 | 'rgba(153, 255, 102, 0.5)', 176 | 'rgba(102, 204, 51, 0.5)', 177 | 'rgba(51, 153, 0, 0.5)', 178 | 'rgba(153, 255, 51, 0.5)', 179 | 'rgba(102, 204, 0, 0.5)', 180 | 'rgba(153, 255, 0, 0.5)', 181 | 'rgba(204, 255, 153, 0.5)', 182 | 'rgba(153, 204, 102, 0.5)', 183 | 'rgba(102, 153, 51, 0.5)', 184 | 'rgba(51, 102, 0, 0.5)', 185 | 'rgba(204, 255, 102, 0.5)', 186 | 'rgba(153, 204, 51, 0.5)', 187 | 'rgba(102, 153, 0, 0.5)', 188 | 'rgba(204, 255, 51, 0.5)', 189 | 'rgba(153, 204, 0, 0.5)', 190 | 'rgba(204, 255, 0, 0.5)', 191 | 'rgba(255, 255, 0, 0.5)', 192 | 'rgba(255, 255, 51, 0.5)', 193 | 'rgba(204, 204, 0, 0.5)', 194 | 'rgba(255, 255, 102, 0.5)', 195 | 'rgba(204, 204, 51, 0.5)', 196 | 'rgba(153, 153, 0, 0.5)', 197 | 'rgba(255, 255, 153, 0.5)', 198 | 'rgba(204, 204, 102, 0.5)', 199 | 'rgba(153, 153, 51, 0.5)', 200 | 'rgba(102, 102, 0, 0.5)', 201 | 'rgba(255, 255, 204, 0.5)', 202 | 'rgba(204, 204, 153, 0.5)', 203 | 'rgba(153, 153, 102, 0.5)', 204 | 'rgba(102, 102, 51, 0.5)', 205 | 'rgba(51, 51, 0, 0.5)', 206 | 'rgba(255, 204, 0, 0.5)', 207 | 'rgba(255, 204, 51, 0.5)', 208 | 'rgba(204, 153, 0, 0.5)', 209 | 'rgba(255, 204, 102, 0.5)', 210 | 'rgba(204, 153, 51, 0.5)', 211 | 'rgba(153, 102, 0, 0.5)', 212 | 'rgba(255, 204, 153, 0.5)', 213 | 'rgba(204, 153, 102, 0.5)', 214 | 'rgba(153, 102, 51, 0.5)', 215 | 'rgba(102, 51, 0, 0.5)', 216 | 'rgba(255, 153, 0, 0.5)', 217 | 'rgba(255, 153, 51, 0.5)', 218 | 'rgba(204, 102, 0, 0.5)', 219 | 'rgba(255, 153, 102, 0.5)', 220 | 'rgba(204, 102, 51, 0.5)', 221 | 'rgba(153, 51, 0, 0.5)', 222 | 'rgba(255, 102, 0, 0.5)', 223 | 'rgba(255, 102, 51, 0.5)', 224 | 'rgba(204, 51, 0, 0.5)', 225 | 'rgba(255, 51, 0, 0.5)', 226 | 'rgba(255, 0, 0, 0.5)', 227 | 'rgba(255, 51, 51, 0.5)', 228 | 'rgba(204, 0, 0, 0.5)', 229 | 'rgba(255, 102, 102, 0.5)', 230 | 'rgba(204, 51, 51, 0.5)', 231 | 'rgba(153, 0, 0, 0.5)', 232 | 'rgba(255, 153, 153, 0.5)', 233 | 'rgba(204, 102, 102, 0.5)', 234 | 'rgba(153, 51, 51, 0.5)', 235 | 'rgba(102, 0, 0, 0.5)', 236 | 'rgba(255, 204, 204, 0.5)', 237 | 'rgba(204, 153, 153, 0.5)', 238 | 'rgba(153, 102, 102, 0.5)', 239 | 'rgba(102, 51, 51, 0.5)', 240 | 'rgba(51, 0, 0, 0.5)', 241 | 'rgba(255, 0, 51, 0.5)', 242 | 'rgba(255, 51, 102, 0.5)', 243 | 'rgba(204, 0, 51, 0.5)', 244 | 'rgba(255, 0, 102, 0.5)', 245 | 'rgba(255, 102, 153, 0.5)', 246 | 'rgba(204, 51, 102, 0.5)', 247 | 'rgba(153, 0, 51, 0.5)', 248 | 'rgba(255, 51, 153, 0.5)', 249 | 'rgba(204, 0, 102, 0.5)', 250 | 'rgba(255, 0, 153, 0.5)', 251 | 'rgba(255, 153, 204, 0.5)', 252 | 'rgba(204, 102, 153, 0.5)', 253 | 'rgba(153, 51, 102, 0.5)', 254 | 'rgba(102, 0, 51, 0.5)', 255 | 'rgba(255, 102, 204, 0.5)', 256 | 'rgba(204, 51, 153, 0.5)', 257 | 'rgba(153, 0, 102, 0.5)', 258 | 'rgba(255, 51, 204, 0.5)', 259 | 'rgba(204, 0, 153, 0.5)', 260 | 'rgba(255, 0, 204, 0.5)', 261 | ]; 262 | -------------------------------------------------------------------------------- /performance/scriptLinear.js: -------------------------------------------------------------------------------- 1 | import CRDT from '../lib/crdtLinear'; 2 | import * as Util from './utilLinear'; 3 | import fs from 'fs'; 4 | import UUID from 'uuid/v1'; 5 | 6 | const logPath = 'performance/logs'; 7 | 8 | export function mockController() { 9 | return { 10 | siteId: UUID(), 11 | broadcastInsertion: function() {}, 12 | broadcastDeletion: function() {}, 13 | insertIntoEditor: function() {}, 14 | deleteFromEditor: function() {}, 15 | vector: { 16 | localVersion: { 17 | counter: 0 18 | }, 19 | increment: function() { 20 | this.localVersion.counter++; 21 | } 22 | } 23 | } 24 | } 25 | 26 | const crdt1 = new CRDT(mockController()); 27 | const crdt2 = new CRDT(mockController()); 28 | const crdt3 = new CRDT(mockController()); 29 | const crdt4 = new CRDT(mockController()); 30 | const crdt5 = new CRDT(mockController()); 31 | 32 | [crdt1, crdt2, crdt3, crdt4, crdt5].forEach(crdt => { 33 | crdt.insertText = function() {}; 34 | crdt.deleteText = function() {}; 35 | }); 36 | 37 | let table = ` 38 | #### PERFORMANCE METRICS (Linear) 39 | Base: ${crdt1.base} | Boundary: ${crdt1.boundary} | Strategy: ${crdt1.strategy} 40 | ================================================================================================ 41 | 42 | ## RANDOM 43 | --------- 44 | 45 | # LOCAL INSERTIONS 46 | ----------------------------------------------------------------------------------------------- 47 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 48 | | | (in milliseconds) | (in milliseconds) | | Length | 49 | ----------------------------------------------------------------------------------------------- 50 | 51 | ${Util.addRowWithId(10, crdt1, Util.insertRandom)} 52 | ${Util.addRowWithId(100, crdt2, Util.insertRandom)} 53 | ${Util.addRowWithId(1000, crdt3, Util.insertRandom)} 54 | ${Util.addRowWithId(10000, crdt4, Util.insertRandom)} 55 | ${Util.addRowWithId(100000, crdt5, Util.insertRandom)} 56 | 57 | # LOCAL DELETIONS 58 | -------------------------------------------------------------- 59 | | # of Operations | Total Execute Time | Avg. Operation Time | 60 | | | (in milliseconds) | (in milliseconds) | 61 | -------------------------------------------------------------- 62 | ${Util.addRow(10, crdt1, Util.deleteRandom)} 63 | ${Util.addRow(100, crdt2, Util.deleteRandom)} 64 | ${Util.addRow(1000, crdt3, Util.deleteRandom)} 65 | ${Util.addRow(10000, crdt4, Util.deleteRandom)} 66 | ${Util.addRow(100000, crdt5, Util.deleteRandom)} 67 | 68 | # REMOTE INSERTIONS 69 | -------------------------------------------------------------- 70 | | # of Operations | Total Execute Time | Avg. Operation Time | 71 | | | (in milliseconds) | (in milliseconds) | 72 | -------------------------------------------------------------- 73 | ${Util.addRow(10, crdt1, Util.remoteInsertRandom)} 74 | ${Util.addRow(100, crdt2, Util.remoteInsertRandom)} 75 | ${Util.addRow(1000, crdt3, Util.remoteInsertRandom)} 76 | ${Util.addRow(10000, crdt4, Util.remoteInsertRandom)} 77 | ${Util.addRow(100000, crdt5, Util.remoteInsertRandom)} 78 | 79 | # REMOTE DELETIONS 80 | -------------------------------------------------------------- 81 | | # of Operations | Total Execute Time | Avg. Operation Time | 82 | | | (in milliseconds) | (in milliseconds) | 83 | -------------------------------------------------------------- 84 | ${Util.addRow(10, crdt1, Util.remoteDeleteRandom)} 85 | ${Util.addRow(100, crdt2, Util.remoteDeleteRandom)} 86 | ${Util.addRow(1000, crdt3, Util.remoteDeleteRandom)} 87 | ${Util.addRow(10000, crdt4, Util.remoteDeleteRandom)} 88 | ${Util.addRow(100000, crdt5, Util.remoteDeleteRandom)} 89 | 90 | 91 | ## AT THE BEGINNING 92 | ------------------- 93 | 94 | # LOCAL INSERTIONS 95 | ----------------------------------------------------------------------------------------------- 96 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 97 | | | (in milliseconds) | (in milliseconds) | | Length | 98 | ----------------------------------------------------------------------------------------------- 99 | ${Util.addRowWithId(10, crdt1, Util.insertBeginning)} 100 | ${Util.addRowWithId(100, crdt2, Util.insertBeginning)} 101 | ${Util.addRowWithId(1000, crdt3, Util.insertBeginning)} 102 | ${Util.addRowWithId(10000, crdt4, Util.insertBeginning)} 103 | ${Util.addRowWithId(100000, crdt5, Util.insertBeginning)} 104 | 105 | # LOCAL DELETIONS 106 | -------------------------------------------------------------- 107 | | # of Operations | Total Execute Time | Avg. Operation Time | 108 | | | (in milliseconds) | (in milliseconds) | 109 | -------------------------------------------------------------- 110 | ${Util.addRow(10, crdt1, Util.deleteBeginning)} 111 | ${Util.addRow(100, crdt2, Util.deleteBeginning)} 112 | ${Util.addRow(1000, crdt3, Util.deleteBeginning)} 113 | ${Util.addRow(10000, crdt4, Util.deleteBeginning)} 114 | ${Util.addRow(100000, crdt5, Util.deleteBeginning)} 115 | 116 | # REMOTE INSERTIONS 117 | -------------------------------------------------------------- 118 | | # of Operations | Total Execute Time | Avg. Operation Time | 119 | | | (in milliseconds) | (in milliseconds) | 120 | -------------------------------------------------------------- 121 | ${Util.addRow(10, crdt1, Util.remoteInsertBeginning)} 122 | ${Util.addRow(100, crdt2, Util.remoteInsertBeginning)} 123 | ${Util.addRow(1000, crdt3, Util.remoteInsertBeginning)} 124 | ${Util.addRow(10000, crdt4, Util.remoteInsertBeginning)} 125 | ${Util.addRow(100000, crdt5, Util.remoteInsertBeginning)} 126 | 127 | # REMOTE DELETIONS 128 | -------------------------------------------------------------- 129 | | # of Operations | Total Execute Time | Avg. Operation Time | 130 | | | (in milliseconds) | (in milliseconds) | 131 | -------------------------------------------------------------- 132 | ${Util.addRow(10, crdt1, Util.remoteDeleteBeginning)} 133 | ${Util.addRow(100, crdt2, Util.remoteDeleteBeginning)} 134 | ${Util.addRow(1000, crdt3, Util.remoteDeleteBeginning)} 135 | ${Util.addRow(10000, crdt4, Util.remoteDeleteBeginning)} 136 | ${Util.addRow(100000, crdt5, Util.remoteDeleteBeginning)} 137 | 138 | 139 | ## AT THE END 140 | ------------- 141 | 142 | # LOCAL INSERTIONS 143 | ----------------------------------------------------------------------------------------------- 144 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 145 | | | (in milliseconds) | (in milliseconds) | | Length | 146 | ----------------------------------------------------------------------------------------------- 147 | ${Util.addRowWithId(10, crdt1, Util.insertEnd)} 148 | ${Util.addRowWithId(100, crdt2, Util.insertEnd)} 149 | ${Util.addRowWithId(1000, crdt3, Util.insertEnd)} 150 | ${Util.addRowWithId(10000, crdt4, Util.insertEnd)} 151 | ${Util.addRowWithId(100000, crdt5, Util.insertEnd)} 152 | 153 | # LOCAL DELETIONS 154 | -------------------------------------------------------------- 155 | | # of Operations | Total Execute Time | Avg. Operation Time | 156 | | | (in milliseconds) | (in milliseconds) | 157 | -------------------------------------------------------------- 158 | ${Util.addRow(10, crdt1, Util.deleteEnd)} 159 | ${Util.addRow(100, crdt2, Util.deleteEnd)} 160 | ${Util.addRow(1000, crdt3, Util.deleteEnd)} 161 | ${Util.addRow(10000, crdt4, Util.deleteEnd)} 162 | ${Util.addRow(100000, crdt5, Util.deleteEnd)} 163 | 164 | # REMOTE INSERTIONS 165 | -------------------------------------------------------------- 166 | | # of Operations | Total Execute Time | Avg. Operation Time | 167 | | | (in milliseconds) | (in milliseconds) | 168 | -------------------------------------------------------------- 169 | ${Util.addRow(10, crdt1, Util.remoteInsertEnd)} 170 | ${Util.addRow(100, crdt2, Util.remoteInsertEnd)} 171 | ${Util.addRow(1000, crdt3, Util.remoteInsertEnd)} 172 | ${Util.addRow(10000, crdt4, Util.remoteInsertEnd)} 173 | ${Util.addRow(100000, crdt5, Util.remoteInsertEnd)} 174 | 175 | # REMOTE DELETIONS 176 | -------------------------------------------------------------- 177 | | # of Operations | Total Execute Time | Avg. Operation Time | 178 | | | (in milliseconds) | (in milliseconds) | 179 | -------------------------------------------------------------- 180 | ${Util.addRow(10, crdt1, Util.remoteDeleteEnd)} 181 | ${Util.addRow(100, crdt2, Util.remoteDeleteEnd)} 182 | ${Util.addRow(1000, crdt3, Util.remoteDeleteEnd)} 183 | ${Util.addRow(10000, crdt4, Util.remoteDeleteEnd)} 184 | ${Util.addRow(100000, crdt5, Util.remoteDeleteEnd)} 185 | `; 186 | 187 | fs.writeFile(`${logPath}/${Util.getTimestamp()}.log`, table, function(err) { 188 | if (err) { 189 | return console.log(err); 190 | } 191 | 192 | console.log(`Results saved to ${logPath}`) 193 | }) 194 | -------------------------------------------------------------------------------- /performance/script.js: -------------------------------------------------------------------------------- 1 | import CRDT from '../lib/crdt'; 2 | import * as Util from './util'; 3 | import fs from 'fs'; 4 | import UUID from 'uuid/v1'; 5 | 6 | const logPath = 'performance/logs'; 7 | 8 | export function mockController() { 9 | return { 10 | siteId: UUID(), 11 | broadcastInsertion: function() {}, 12 | broadcastDeletion: function() {}, 13 | insertIntoEditor: function() {}, 14 | deleteFromEditor: function() {}, 15 | vector: { 16 | getLocalVersion: () => {}, 17 | localVersion: { 18 | counter: 0 19 | }, 20 | increment: function() { 21 | this.localVersion.counter++; 22 | } 23 | } 24 | } 25 | } 26 | 27 | const crdt1 = new CRDT(mockController()); 28 | const crdt2 = new CRDT(mockController()); 29 | const crdt3 = new CRDT(mockController()); 30 | const crdt4 = new CRDT(mockController()); 31 | const crdt5 = new CRDT(mockController()); 32 | const crdt6 = new CRDT(mockController()); 33 | 34 | let table = ` 35 | #### PERFORMANCE METRICS (Array of Arrays) 36 | Base: ${crdt1.base} | Boundary: ${crdt1.boundary} | Strategy: ${crdt1.strategy} 37 | ================================================================================================ 38 | 39 | ## RANDOM 40 | --------- 41 | 42 | # LOCAL INSERTIONS 43 | ----------------------------------------------------------------------------------------------- 44 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 45 | | | (in milliseconds) | (in milliseconds) | | Length | 46 | ----------------------------------------------------------------------------------------------- 47 | 48 | ${Util.addRowWithId(10, crdt1, Util.insertRandom)} 49 | ${Util.addRowWithId(100, crdt2, Util.insertRandom)} 50 | ${Util.addRowWithId(1000, crdt3, Util.insertRandom)} 51 | ${Util.addRowWithId(10000, crdt4, Util.insertRandom)} 52 | ${Util.addRowWithId(100000, crdt5, Util.insertRandom)} 53 | ${Util.addRowWithId(1000000, crdt6, Util.insertRandom)} 54 | 55 | 56 | # LOCAL DELETIONS 57 | -------------------------------------------------------------- 58 | | # of Operations | Total Execute Time | Avg. Operation Time | 59 | | | (in milliseconds) | (in milliseconds) | 60 | -------------------------------------------------------------- 61 | ${Util.addRow(10, crdt1, Util.deleteRandom)} 62 | ${Util.addRow(100, crdt2, Util.deleteRandom)} 63 | ${Util.addRow(1000, crdt3, Util.deleteRandom)} 64 | ${Util.addRow(10000, crdt4, Util.deleteRandom)} 65 | ${Util.addRow(100000, crdt5, Util.deleteRandom)} 66 | ${Util.addRow(1000000, crdt6, Util.deleteRandom)} 67 | 68 | 69 | # REMOTE INSERTIONS 70 | -------------------------------------------------------------- 71 | | # of Operations | Total Execute Time | Avg. Operation Time | 72 | | | (in milliseconds) | (in milliseconds) | 73 | -------------------------------------------------------------- 74 | ${Util.addRow(10, crdt1, Util.remoteInsertRandom)} 75 | ${Util.addRow(100, crdt2, Util.remoteInsertRandom)} 76 | ${Util.addRow(1000, crdt3, Util.remoteInsertRandom)} 77 | ${Util.addRow(10000, crdt4, Util.remoteInsertRandom)} 78 | ${Util.addRow(100000, crdt5, Util.remoteInsertRandom)} 79 | 80 | 81 | # REMOTE DELETIONS 82 | -------------------------------------------------------------- 83 | | # of Operations | Total Execute Time | Avg. Operation Time | 84 | | | (in milliseconds) | (in milliseconds) | 85 | -------------------------------------------------------------- 86 | ${Util.addRow(10, crdt1, Util.remoteDeleteRandom)} 87 | ${Util.addRow(100, crdt2, Util.remoteDeleteRandom)} 88 | ${Util.addRow(1000, crdt3, Util.remoteDeleteRandom)} 89 | ${Util.addRow(10000, crdt4, Util.remoteDeleteRandom)} 90 | ${Util.addRow(100000, crdt5, Util.remoteDeleteRandom)} 91 | 92 | 93 | 94 | ## AT THE BEGINNING 95 | ------------------- 96 | 97 | # LOCAL INSERTIONS 98 | ----------------------------------------------------------------------------------------------- 99 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 100 | | | (in milliseconds) | (in milliseconds) | | Length | 101 | ----------------------------------------------------------------------------------------------- 102 | ${Util.addRowWithId(10, crdt1, Util.insertBeginning)} 103 | ${Util.addRowWithId(100, crdt2, Util.insertBeginning)} 104 | ${Util.addRowWithId(1000, crdt3, Util.insertBeginning)} 105 | ${Util.addRowWithId(10000, crdt4, Util.insertBeginning)} 106 | ${Util.addRowWithId(100000, crdt5, Util.insertBeginning)} 107 | 108 | 109 | # LOCAL DELETIONS 110 | -------------------------------------------------------------- 111 | | # of Operations | Total Execute Time | Avg. Operation Time | 112 | | | (in milliseconds) | (in milliseconds) | 113 | -------------------------------------------------------------- 114 | ${Util.addRow(10, crdt1, Util.deleteBeginning)} 115 | ${Util.addRow(100, crdt2, Util.deleteBeginning)} 116 | ${Util.addRow(1000, crdt3, Util.deleteBeginning)} 117 | ${Util.addRow(10000, crdt4, Util.deleteBeginning)} 118 | ${Util.addRow(100000, crdt5, Util.deleteBeginning)} 119 | 120 | 121 | # REMOTE INSERTIONS 122 | -------------------------------------------------------------- 123 | | # of Operations | Total Execute Time | Avg. Operation Time | 124 | | | (in milliseconds) | (in milliseconds) | 125 | -------------------------------------------------------------- 126 | ${Util.addRow(10, crdt1, Util.remoteInsertBeginning)} 127 | ${Util.addRow(100, crdt2, Util.remoteInsertBeginning)} 128 | ${Util.addRow(1000, crdt3, Util.remoteInsertBeginning)} 129 | ${Util.addRow(10000, crdt4, Util.remoteInsertBeginning)} 130 | ${Util.addRow(100000, crdt5, Util.remoteInsertBeginning)} 131 | 132 | 133 | # REMOTE DELETIONS 134 | -------------------------------------------------------------- 135 | | # of Operations | Total Execute Time | Avg. Operation Time | 136 | | | (in milliseconds) | (in milliseconds) | 137 | -------------------------------------------------------------- 138 | ${Util.addRow(10, crdt1, Util.remoteDeleteBeginning)} 139 | ${Util.addRow(100, crdt2, Util.remoteDeleteBeginning)} 140 | ${Util.addRow(1000, crdt3, Util.remoteDeleteBeginning)} 141 | ${Util.addRow(10000, crdt4, Util.remoteDeleteBeginning)} 142 | ${Util.addRow(100000, crdt5, Util.remoteDeleteBeginning)} 143 | 144 | 145 | 146 | ## AT THE END 147 | ------------- 148 | 149 | # LOCAL INSERTIONS 150 | ----------------------------------------------------------------------------------------------- 151 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 152 | | | (in milliseconds) | (in milliseconds) | | Length | 153 | ----------------------------------------------------------------------------------------------- 154 | ${Util.addRowWithId(10, crdt1, Util.insertEnd)} 155 | ${Util.addRowWithId(100, crdt2, Util.insertEnd)} 156 | ${Util.addRowWithId(1000, crdt3, Util.insertEnd)} 157 | ${Util.addRowWithId(10000, crdt4, Util.insertEnd)} 158 | ${Util.addRowWithId(100000, crdt5, Util.insertEnd)} 159 | 160 | 161 | # LOCAL DELETIONS 162 | -------------------------------------------------------------- 163 | | # of Operations | Total Execute Time | Avg. Operation Time | 164 | | | (in milliseconds) | (in milliseconds) | 165 | -------------------------------------------------------------- 166 | ${Util.addRow(10, crdt1, Util.deleteEnd)} 167 | ${Util.addRow(100, crdt2, Util.deleteEnd)} 168 | ${Util.addRow(1000, crdt3, Util.deleteEnd)} 169 | ${Util.addRow(10000, crdt4, Util.deleteEnd)} 170 | ${Util.addRow(100000, crdt5, Util.deleteEnd)} 171 | 172 | 173 | # REMOTE INSERTIONS 174 | -------------------------------------------------------------- 175 | | # of Operations | Total Execute Time | Avg. Operation Time | 176 | | | (in milliseconds) | (in milliseconds) | 177 | -------------------------------------------------------------- 178 | ${Util.addRow(10, crdt1, Util.remoteInsertEnd)} 179 | ${Util.addRow(100, crdt2, Util.remoteInsertEnd)} 180 | ${Util.addRow(1000, crdt3, Util.remoteInsertEnd)} 181 | ${Util.addRow(10000, crdt4, Util.remoteInsertEnd)} 182 | ${Util.addRow(100000, crdt5, Util.remoteInsertEnd)} 183 | 184 | 185 | # REMOTE DELETIONS 186 | -------------------------------------------------------------- 187 | | # of Operations | Total Execute Time | Avg. Operation Time | 188 | | | (in milliseconds) | (in milliseconds) | 189 | -------------------------------------------------------------- 190 | ${Util.addRow(10, crdt1, Util.remoteDeleteEnd)} 191 | ${Util.addRow(100, crdt2, Util.remoteDeleteEnd)} 192 | ${Util.addRow(1000, crdt3, Util.remoteDeleteEnd)} 193 | ${Util.addRow(10000, crdt4, Util.remoteDeleteEnd)} 194 | ${Util.addRow(100000, crdt5, Util.remoteDeleteEnd)} 195 | 196 | `; 197 | 198 | fs.writeFile(`${logPath}/${Util.getTimestamp()}.log`, table, function(err) { 199 | if (err) { 200 | return console.log(err); 201 | } 202 | 203 | console.log(`Results saved to ${logPath}`) 204 | }) 205 | -------------------------------------------------------------------------------- /performance/util.js: -------------------------------------------------------------------------------- 1 | import Char from '../lib/char'; 2 | import CRDT from '../lib/crdt'; 3 | import { mockController } from './script'; 4 | 5 | const CELL_1_SIZE = 17; 6 | const CELL_2_SIZE = 20; 7 | const CELL_3_SIZE = 21; 8 | const CELL_4_SIZE = 16; 9 | const CELL_5_SIZE = 15; 10 | 11 | function insertRandom(crdt, numberOfOperations) { 12 | let counter = 0; 13 | let line = 0; 14 | let ch, pos; 15 | const start = Date.now(); 16 | 17 | for (let i = 0; i < numberOfOperations; i++) { 18 | if (counter === 100) { 19 | pos = { line: line, ch: counter} 20 | crdt.handleLocalInsert('\n', pos); 21 | 22 | line++; 23 | counter = 0; 24 | } else { 25 | ch = Math.floor(Math.random() * counter); 26 | pos = { line: line, ch: ch }; 27 | crdt.handleLocalInsert('a', pos); 28 | 29 | counter++ 30 | } 31 | } 32 | 33 | const end = Date.now(); 34 | return end - start; 35 | } 36 | 37 | function remoteInsertRandom(crdt, numberOfOperations) { 38 | const chars = shuffle(generateChars(numberOfOperations)); 39 | return remoteInsert(crdt, chars); 40 | } 41 | 42 | function remoteInsert(crdt, chars) { 43 | const start = Date.now(); 44 | 45 | chars.forEach(char => crdt.handleRemoteInsert(char)); 46 | 47 | const end = Date.now(); 48 | return end - start; 49 | } 50 | 51 | function deleteRandom(crdt) { 52 | const totalChars = crdt.totalChars(); 53 | const start = Date.now(); 54 | let line, ch, startPos, endPos; 55 | 56 | for(let i = totalChars; i > 0; i--) { 57 | line = Math.floor(Math.random() * crdt.struct.length) 58 | ch = Math.floor(Math.random() * crdt.struct[line].length); 59 | startPos = { line: line, ch: ch } 60 | endPos = { line: line, ch: ch + 1 } 61 | crdt.handleLocalDelete(startPos, endPos); 62 | } 63 | 64 | const end = Date.now(); 65 | return end - start; 66 | } 67 | 68 | function remoteDeleteRandom(crdt) { 69 | const charsToDelete = [].concat.apply([], crdt.struct); 70 | 71 | return remoteDelete(crdt, shuffle(charsToDelete)); 72 | } 73 | 74 | function remoteDelete(crdt, chars) { 75 | const start = Date.now(); 76 | 77 | chars.forEach(char => crdt.handleRemoteDelete(char)); 78 | 79 | const end = Date.now(); 80 | return end - start; 81 | } 82 | 83 | function insertBeginning(crdt, numberOfOperations) { 84 | let counter = 0; 85 | let line = 0; 86 | let ch, pos; 87 | const start = Date.now(); 88 | 89 | for (let i = 0; i < numberOfOperations; i++) { 90 | if (counter === 100) { 91 | pos = { line: line, ch: counter }; 92 | crdt.handleLocalInsert('\n', pos); 93 | 94 | line++; 95 | counter = 0; 96 | } else { 97 | pos = { line: line, ch: 0 }; 98 | crdt.handleLocalInsert('a', pos); 99 | 100 | counter++ 101 | } 102 | } 103 | 104 | const end = Date.now(); 105 | return end - start; 106 | } 107 | 108 | function deleteBeginning(crdt) { 109 | const totalChars = crdt.totalChars(); 110 | const start = Date.now(); 111 | 112 | for (let i = totalChars; i > 0; i--) { 113 | let startPos = { line: 0, ch: 0 }; 114 | let endPos = { line: 0, ch: 1}; 115 | 116 | crdt.handleLocalDelete(startPos, endPos); 117 | } 118 | 119 | const end = Date.now(); 120 | return end - start; 121 | } 122 | 123 | function remoteInsertBeginning(crdt, numberOfOperations) { 124 | const chars = generateChars(numberOfOperations); 125 | const descChars = chars.reverse(); 126 | 127 | return remoteInsert(crdt, descChars); 128 | } 129 | 130 | function remoteDeleteBeginning(crdt) { 131 | const charsToDelete = [].concat.apply([], crdt.struct); 132 | return remoteDelete(crdt, charsToDelete); 133 | } 134 | 135 | function insertEnd(crdt, numberOfOperations) { 136 | let counter = 0; 137 | let line = 0; 138 | let ch, pos; 139 | const start = Date.now(); 140 | 141 | for (let i = 0; i < numberOfOperations; i++) { 142 | pos = { line: line, ch: counter }; 143 | 144 | if (counter === 100) { 145 | crdt.handleLocalInsert('\n', pos); 146 | 147 | line++; 148 | counter = 0; 149 | } else { 150 | crdt.handleLocalInsert('a', pos); 151 | 152 | counter++ 153 | } 154 | } 155 | 156 | const end = Date.now(); 157 | return end - start; 158 | } 159 | 160 | function deleteEnd(crdt) { 161 | const totalChars = crdt.totalChars(); 162 | const start = Date.now(); 163 | let line; 164 | let ch; 165 | let lineNum; 166 | 167 | for (let i = totalChars; i > 0; i--) { 168 | lineNum = crdt.struct.length - 1; 169 | line = crdt.struct[lineNum]; 170 | ch = line[line.length - 1]; 171 | let startPos = { line: lineNum, ch: ch }; 172 | let endPos = { line: lineNum, ch: ch + 1}; 173 | 174 | crdt.handleLocalDelete(startPos, endPos); 175 | } 176 | 177 | const end = Date.now(); 178 | return end - start; 179 | } 180 | 181 | function remoteInsertEnd(crdt, numberOfOperations) { 182 | const ascChars = generateChars(numberOfOperations); 183 | 184 | return remoteInsert(crdt, ascChars); 185 | } 186 | 187 | function remoteDeleteEnd(crdt) { 188 | const charsToDelete = [].concat.apply([], crdt.struct); 189 | const reverseToDel = charsToDelete.reverse(); 190 | return remoteDelete(crdt, reverseToDel); 191 | } 192 | 193 | function generateChars(numOps) { 194 | let crdts = []; 195 | let crdt; 196 | 197 | // Create crdts based on number of operations requested 198 | for (let i = 0; i < Math.log10(numOps); i++) { 199 | crdt = new CRDT(mockController()); 200 | crdts.push(crdt); 201 | } 202 | 203 | // Insert characters randomly in each crdt 204 | const numOpsPerCRDT = numOps / crdts.length; 205 | crdts.forEach(crdt => insertRandom(crdt, numOpsPerCRDT)); 206 | 207 | let chars = []; 208 | const structsWithLines = crdts.map(crdt => crdt.struct); 209 | const structs = structsWithLines.map(struct => { 210 | return [].concat.apply([], struct); 211 | }); 212 | 213 | for (let i = 0; i < structs[0].length; i++) { 214 | structs.forEach(struct => chars.push(struct[i])); 215 | } 216 | 217 | return chars; 218 | } 219 | 220 | function shuffle(a) { 221 | for (let i = a.length - 1; i > 0; i--) { 222 | const j = Math.floor(Math.random() * (i + 1)); 223 | [a[i], a[j]] = [a[j], a[i]]; 224 | } 225 | return a; 226 | } 227 | 228 | function avgIdLength(crdt) { 229 | let numChars = 0; 230 | 231 | const idArray = crdt.struct.map(line => line.map(char => char.position.map(id => id.digit).join(''))); 232 | const digitLengthSum = idArray.reduce((acc, line) => { 233 | return acc + line.reduce((acc, id) => { 234 | numChars++; 235 | return acc + id.length; 236 | }, 0); 237 | }, 0); 238 | 239 | return Math.floor(digitLengthSum / numChars); 240 | } 241 | 242 | function avgPosLength(crdt) { 243 | let numChars = 0; 244 | 245 | const posArray = crdt.struct.map(line => line.map(char => char.position.length)); 246 | const posLengthSum = posArray.reduce((acc, line) => { 247 | return acc + line.reduce((acc, len) => { 248 | numChars++; 249 | return acc + len; 250 | }, 0); 251 | }, 0); 252 | 253 | return Math.floor(posLengthSum / numChars); 254 | } 255 | 256 | function average(time, operations) { 257 | return time / operations; 258 | } 259 | 260 | function addPadding(value, cellSize) { 261 | value = String(value); 262 | 263 | if (value.length > cellSize) { 264 | value = value.slice(0, cellSize); 265 | } 266 | 267 | const padding = ((cellSize - value.length) / 2); 268 | return (' ').repeat(Math.floor(padding)) + value + (' ').repeat(Math.ceil(padding)); 269 | } 270 | 271 | function addRowWithId(operations, crdt, func) { 272 | const totalTime = func(crdt, operations); 273 | const cell1 = addPadding(operations, CELL_1_SIZE); 274 | const cell2 = addPadding(totalTime, CELL_2_SIZE); 275 | const cell3 = addPadding(average(totalTime, operations), CELL_3_SIZE); 276 | const cell4 = addPadding(avgIdLength(crdt), CELL_4_SIZE); 277 | const cell5 = addPadding(avgPosLength(crdt), CELL_5_SIZE); 278 | 279 | return `|${cell1}|${cell2}|${cell3}|${cell4}|${cell5}| 280 | ${'-'.repeat(95)}` 281 | 282 | } 283 | 284 | function addRow(operations, crdt, func) { 285 | const totalTime = func(crdt, operations); 286 | const cell1 = addPadding(operations, CELL_1_SIZE); 287 | const cell2 = addPadding(totalTime, CELL_2_SIZE); 288 | const cell3 = addPadding(average(totalTime, operations), CELL_3_SIZE); 289 | 290 | return `|${cell1}|${cell2}|${cell3}| 291 | ${'-'.repeat(62)}` 292 | } 293 | 294 | function getTimestamp() { 295 | const now = new Date(); 296 | const year = now.getUTCFullYear(); 297 | const month = now.getUTCMonth() + 1; 298 | const date = now.getUTCDate(); 299 | const hours = now.getUTCHours(); 300 | const minutes = now.getUTCMinutes(); 301 | const seconds = now.getUTCSeconds(); 302 | 303 | return `${year}-${month}-${date} ${hours}:${minutes}:${seconds}`; 304 | } 305 | 306 | export { 307 | addRow, 308 | addRowWithId, 309 | insertRandom, 310 | remoteInsertRandom, 311 | insertEnd, 312 | remoteInsertEnd, 313 | insertBeginning, 314 | remoteInsertBeginning, 315 | deleteRandom, 316 | remoteDeleteRandom, 317 | deleteEnd, 318 | remoteDeleteEnd, 319 | deleteBeginning, 320 | remoteDeleteBeginning, 321 | getTimestamp 322 | }; 323 | -------------------------------------------------------------------------------- /lib/broadcast.js: -------------------------------------------------------------------------------- 1 | class Broadcast { 2 | constructor() { 3 | this.controller = null; 4 | this.peer = null; 5 | this.outConns = []; 6 | this.inConns = []; 7 | this.outgoingBuffer = []; 8 | this.MAX_BUFFER_SIZE = 40; 9 | this.currentStream = null; 10 | } 11 | 12 | send(operation) { 13 | const operationJSON = JSON.stringify(operation); 14 | if (operation.type === 'insert' || operation.type === 'delete') { 15 | this.addToOutgoingBuffer(operationJSON); 16 | } 17 | this.outConns.forEach(conn => conn.send(operationJSON)); 18 | } 19 | 20 | addToOutgoingBuffer(operation) { 21 | if (this.outgoingBuffer.length === this.MAX_BUFFER_SIZE) { 22 | this.outgoingBuffer.shift(); 23 | } 24 | 25 | this.outgoingBuffer.push(operation); 26 | } 27 | 28 | processOutgoingBuffer(peerId) { 29 | const connection = this.outConns.find(conn => conn.peer === peerId); 30 | this.outgoingBuffer.forEach(op => { 31 | connection.send(op); 32 | }); 33 | } 34 | 35 | bindServerEvents(targetPeerId, peer) { 36 | this.peer = peer; 37 | this.onOpen(targetPeerId); 38 | this.heartbeat = this.startPeerHeartBeat(peer); 39 | } 40 | 41 | startPeerHeartBeat(peer) { 42 | let timeoutId = 0; 43 | const heartbeat = () => { 44 | timeoutId = setTimeout( heartbeat, 20000 ); 45 | if ( peer.socket._wsOpen() ) { 46 | peer.socket.send( {type:'HEARTBEAT'} ); 47 | } 48 | }; 49 | 50 | heartbeat(); 51 | 52 | return { 53 | start : function () { 54 | if ( timeoutId === 0 ) { heartbeat(); } 55 | }, 56 | stop : function () { 57 | clearTimeout( timeoutId ); 58 | timeoutId = 0; 59 | } 60 | }; 61 | } 62 | 63 | onOpen(targetPeerId) { 64 | this.peer.on('open', id => { 65 | this.controller.updateShareLink(id); 66 | this.onPeerConnection(); 67 | this.onError(); 68 | this.onDisconnect(); 69 | if (targetPeerId == 0) { 70 | this.controller.addToNetwork(id, this.controller.siteId); 71 | } else { 72 | this.requestConnection(targetPeerId, id, this.controller.siteId) 73 | } 74 | }); 75 | } 76 | 77 | onError() { 78 | this.peer.on("error", err => { 79 | const pid = String(err).replace("Error: Could not connect to peer ", ""); 80 | this.removeFromConnections(pid); 81 | console.log(err.type); 82 | if (!this.peer.disconnected) { 83 | this.controller.findNewTarget(); 84 | } 85 | this.controller.enableEditor(); 86 | }); 87 | } 88 | 89 | onDisconnect() { 90 | this.peer.on('disconnected', () => { 91 | this.controller.lostConnection(); 92 | }); 93 | } 94 | 95 | requestConnection(target, peerId, siteId) { 96 | const conn = this.peer.connect(target); 97 | this.addToOutConns(conn); 98 | conn.on('open', () => { 99 | conn.send(JSON.stringify({ 100 | type: 'connRequest', 101 | peerId: peerId, 102 | siteId: siteId, 103 | })); 104 | }); 105 | } 106 | 107 | evaluateRequest(peerId, siteId) { 108 | if (this.hasReachedMax()) { 109 | this.forwardConnRequest(peerId, siteId); 110 | } else { 111 | this.acceptConnRequest(peerId, siteId); 112 | } 113 | } 114 | 115 | hasReachedMax() { 116 | const halfTheNetwork = Math.ceil(this.controller.network.length / 2); 117 | const tooManyInConns = this.inConns.length > Math.max(halfTheNetwork, 5); 118 | const tooManyOutConns = this.outConns.length > Math.max(halfTheNetwork, 5); 119 | 120 | return tooManyInConns || tooManyOutConns; 121 | } 122 | 123 | forwardConnRequest(peerId, siteId) { 124 | const connected = this.outConns.filter(conn => conn.peer !== peerId); 125 | const randomIdx = Math.floor(Math.random() * connected.length); 126 | connected[randomIdx].send(JSON.stringify({ 127 | type: 'connRequest', 128 | peerId: peerId, 129 | siteId: siteId, 130 | })); 131 | } 132 | 133 | addToOutConns(connection) { 134 | if (!!connection && !this.isAlreadyConnectedOut(connection)) { 135 | this.outConns.push(connection); 136 | } 137 | } 138 | 139 | addToInConns(connection) { 140 | if (!!connection && !this.isAlreadyConnectedIn(connection)) { 141 | this.inConns.push(connection); 142 | } 143 | } 144 | 145 | addToNetwork(peerId, siteId) { 146 | this.send({ 147 | type: "add to network", 148 | newPeer: peerId, 149 | newSite: siteId 150 | }); 151 | } 152 | 153 | removeFromNetwork(peerId) { 154 | this.send({ 155 | type: "remove from network", 156 | oldPeer: peerId 157 | }); 158 | this.controller.removeFromNetwork(peerId); 159 | } 160 | 161 | removeFromConnections(peer) { 162 | this.inConns = this.inConns.filter(conn => conn.peer !== peer); 163 | this.outConns = this.outConns.filter(conn => conn.peer !== peer); 164 | this.removeFromNetwork(peer); 165 | } 166 | 167 | isAlreadyConnectedOut(connection) { 168 | if (connection.peer) { 169 | return !!this.outConns.find(conn => conn.peer === connection.peer); 170 | } else { 171 | return !!this.outConns.find(conn => conn.peer.id === connection); 172 | } 173 | } 174 | 175 | isAlreadyConnectedIn(connection) { 176 | if (connection.peer) { 177 | return !!this.inConns.find(conn => conn.peer === connection.peer); 178 | } else { 179 | return !!this.inConns.find(conn => conn.peer.id === connection); 180 | } 181 | } 182 | 183 | onPeerConnection() { 184 | this.peer.on('connection', (connection) => { 185 | this.onConnection(connection); 186 | this.onVideoCall(connection); 187 | this.onData(connection); 188 | this.onConnClose(connection); 189 | }); 190 | } 191 | 192 | acceptConnRequest(peerId, siteId) { 193 | const connBack = this.peer.connect(peerId); 194 | this.addToOutConns(connBack); 195 | this.controller.addToNetwork(peerId, siteId); 196 | 197 | const initialData = JSON.stringify({ 198 | type: 'syncResponse', 199 | siteId: this.controller.siteId, 200 | peerId: this.peer.id, 201 | initialStruct: this.controller.crdt.struct, 202 | initialVersions: this.controller.vector.versions, 203 | network: this.controller.network 204 | }); 205 | 206 | if (connBack.open) { 207 | connBack.send(initialData); 208 | } else { 209 | connBack.on('open', () => { 210 | connBack.send(initialData); 211 | }); 212 | } 213 | } 214 | 215 | videoCall(id, ms) { 216 | if (!this.currentStream) { 217 | const callObj = this.peer.call(id, ms); 218 | this.onStream(callObj); 219 | } 220 | } 221 | 222 | onConnection(connection) { 223 | this.controller.updateRootUrl(connection.peer); 224 | this.addToInConns(connection); 225 | } 226 | 227 | onVideoCall() { 228 | this.peer.on('call', callObj => { 229 | this.controller.beingCalled(callObj); 230 | }); 231 | } 232 | 233 | answerCall(callObj, ms) { 234 | if (!this.currentStream) { 235 | callObj.answer(ms); 236 | this.controller.answerCall(callObj.peer); 237 | this.onStream(callObj); 238 | } 239 | } 240 | 241 | onStream(callObj) { 242 | callObj.on('stream', stream => { 243 | if (this.currentStream) { this.currentStream.close(); } 244 | this.currentStream = callObj; 245 | 246 | this.controller.streamVideo(stream, callObj); 247 | 248 | callObj.on('close', () => this.onStreamClose(callObj.peer)) 249 | }); 250 | } 251 | 252 | onStreamClose(peerId) { 253 | this.currentStream.localStream.getTracks().forEach(track => track.stop()); 254 | this.currentStream = null; 255 | 256 | this.controller.closeVideo(peerId); 257 | } 258 | 259 | onData(connection) { 260 | connection.on('data', data => { 261 | const dataObj = JSON.parse(data); 262 | 263 | switch(dataObj.type) { 264 | case 'connRequest': 265 | this.evaluateRequest(dataObj.peerId, dataObj.siteId); 266 | break; 267 | case 'syncResponse': 268 | this.processOutgoingBuffer(dataObj.peerId); 269 | this.controller.handleSync(dataObj); 270 | break; 271 | case 'syncCompleted': 272 | this.processOutgoingBuffer(dataObj.peerId); 273 | break; 274 | case 'add to network': 275 | this.controller.addToNetwork(dataObj.newPeer, dataObj.newSite); 276 | break; 277 | case 'remove from network': 278 | this.controller.removeFromNetwork(dataObj.oldPeer); 279 | break; 280 | default: 281 | this.controller.handleRemoteOperation(dataObj); 282 | } 283 | }); 284 | } 285 | 286 | randomId() { 287 | const possConns = this.inConns.filter(conn => { 288 | return this.peer.id !== conn.peer; 289 | }); 290 | const randomIdx = Math.floor(Math.random() * possConns.length); 291 | if (possConns[randomIdx]) { 292 | return possConns[randomIdx].peer; 293 | } else { 294 | return false; 295 | } 296 | } 297 | 298 | onConnClose(connection) { 299 | connection.on('close', () => { 300 | this.removeFromConnections(connection.peer); 301 | if (connection.peer == this.controller.urlId) { 302 | const id = this.randomId(); 303 | if (id) { this.controller.updatePageURL(id); } 304 | } 305 | if (!this.hasReachedMax()) { 306 | this.controller.findNewTarget(); 307 | } 308 | }); 309 | } 310 | } 311 | 312 | export default Broadcast; 313 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | import Char from './char'; 2 | import CRDT from './crdt'; 3 | import UUID from 'uuid/v1'; 4 | 5 | export function mockController() { 6 | return { 7 | siteId: UUID(), 8 | broadcastInsertion: function() {}, 9 | broadcastDeletion: function() {}, 10 | insertIntoEditor: function() {}, 11 | deleteFromEditor: function() {}, 12 | vector: { 13 | getLocalVersion: () => {}, 14 | localVersion: { 15 | counter: 0 16 | }, 17 | increment: function() { 18 | this.localVersion.counter++; 19 | } 20 | } 21 | } 22 | } 23 | 24 | function insertRandom(crdt, numberOfOperations) { 25 | let counter = 0; 26 | let line = 0; 27 | let ch, pos; 28 | const start = Date.now(); 29 | 30 | for (let i = 0; i < numberOfOperations; i++) { 31 | if (counter === 100) { 32 | pos = { line: line, ch: counter} 33 | crdt.handleLocalInsert('\n', pos); 34 | 35 | line++; 36 | counter = 0; 37 | } else { 38 | ch = Math.floor(Math.random() * counter); 39 | pos = { line: line, ch: ch }; 40 | crdt.handleLocalInsert('a', pos); 41 | `` 42 | counter++ 43 | } 44 | } 45 | 46 | const end = Date.now(); 47 | return end - start; 48 | } 49 | 50 | function remoteInsertRandom(crdt, numberOfOperations) { 51 | const chars = shuffle(generateChars(numberOfOperations)); 52 | return remoteInsert(crdt, chars); 53 | } 54 | 55 | function remoteInsert(crdt, chars) { 56 | const start = Date.now(); 57 | 58 | chars.forEach(char => crdt.handleRemoteInsert(char)); 59 | 60 | const end = Date.now(); 61 | return end - start; 62 | } 63 | 64 | function deleteRandom(crdt) { 65 | const totalChars = crdt.totalChars(); 66 | const start = Date.now(); 67 | let line, ch, startPos, endPos; 68 | 69 | for(let i = totalChars; i > 0; i--) { 70 | line = Math.floor(Math.random() * crdt.struct.length) 71 | ch = Math.floor(Math.random() * crdt.struct[line].length); 72 | startPos = { line: line, ch: ch } 73 | endPos = { line: line, ch: ch + 1 } 74 | crdt.handleLocalDelete(startPos, endPos); 75 | } 76 | 77 | const end = Date.now(); 78 | return end - start; 79 | } 80 | 81 | function remoteDeleteRandom(crdt) { 82 | const charsToDelete = [].concat.apply([], crdt.struct); 83 | 84 | return remoteDelete(crdt, shuffle(charsToDelete)); 85 | } 86 | 87 | function remoteDelete(crdt, chars) { 88 | const start = Date.now(); 89 | 90 | chars.forEach(char => crdt.handleRemoteDelete(char)); 91 | 92 | const end = Date.now(); 93 | return end - start; 94 | } 95 | 96 | function insertBeginning(crdt, numberOfOperations) { 97 | let counter = 0; 98 | let line = 0; 99 | let ch, pos; 100 | const start = Date.now(); 101 | 102 | for (let i = 0; i < numberOfOperations; i++) { 103 | if (counter === 100) { 104 | pos = { line: line, ch: counter }; 105 | crdt.handleLocalInsert('\n', pos); 106 | 107 | line++; 108 | counter = 0; 109 | } else { 110 | pos = { line: line, ch: 0 }; 111 | crdt.handleLocalInsert('a', pos); 112 | 113 | counter++ 114 | } 115 | } 116 | 117 | const end = Date.now(); 118 | return end - start; 119 | } 120 | 121 | function deleteBeginning(crdt) { 122 | const totalChars = crdt.totalChars(); 123 | const start = Date.now(); 124 | 125 | for (let i = totalChars; i > 0; i--) { 126 | let startPos = { line: 0, ch: 0 }; 127 | let endPos = { line: 0, ch: 1}; 128 | 129 | crdt.handleLocalDelete(startPos, endPos); 130 | } 131 | 132 | const end = Date.now(); 133 | return end - start; 134 | } 135 | 136 | function remoteInsertBeginning(crdt, numberOfOperations) { 137 | const chars = generateChars(numberOfOperations); 138 | const descChars = chars.reverse(); 139 | 140 | return remoteInsert(crdt, descChars); 141 | } 142 | 143 | function remoteDeleteBeginning(crdt) { 144 | const charsToDelete = [].concat.apply([], crdt.struct); 145 | return remoteDelete(crdt, charsToDelete); 146 | } 147 | 148 | function insertEnd(crdt, numberOfOperations) { 149 | let counter = 0; 150 | let line = 0; 151 | let ch, pos; 152 | const start = Date.now(); 153 | 154 | for (let i = 0; i < numberOfOperations; i++) { 155 | pos = { line: line, ch: counter }; 156 | 157 | if (counter === 100) { 158 | crdt.handleLocalInsert('\n', pos); 159 | 160 | line++; 161 | counter = 0; 162 | } else { 163 | crdt.handleLocalInsert('a', pos); 164 | 165 | counter++ 166 | } 167 | } 168 | 169 | const end = Date.now(); 170 | return end - start; 171 | } 172 | 173 | function deleteEnd(crdt) { 174 | const totalChars = crdt.totalChars(); 175 | const start = Date.now(); 176 | let line; 177 | let ch; 178 | let lineNum; 179 | 180 | for (let i = totalChars; i > 0; i--) { 181 | lineNum = crdt.struct.length - 1; 182 | line = crdt.struct[lineNum]; 183 | ch = line[line.length - 1]; 184 | let startPos = { line: lineNum, ch: ch }; 185 | let endPos = { line: lineNum, ch: ch + 1}; 186 | 187 | crdt.handleLocalDelete(startPos, endPos); 188 | } 189 | 190 | const end = Date.now(); 191 | return end - start; 192 | } 193 | 194 | function remoteInsertEnd(crdt, numberOfOperations) { 195 | const ascChars = generateChars(numberOfOperations); 196 | 197 | return remoteInsert(crdt, ascChars); 198 | } 199 | 200 | function remoteDeleteEnd(crdt) { 201 | const charsToDelete = [].concat.apply([], crdt.struct); 202 | const reverseToDel = charsToDelete.reverse(); 203 | return remoteDelete(crdt, reverseToDel); 204 | } 205 | 206 | function generateChars(numOps) { 207 | let crdts = []; 208 | let crdt; 209 | 210 | // Create crdts based on number of operations requested 211 | for (let i = 0; i < Math.log10(numOps); i++) { 212 | crdt = new CRDT(mockController()); 213 | crdts.push(crdt); 214 | } 215 | 216 | // Insert characters randomly in each crdt 217 | const numOpsPerCRDT = numOps / crdts.length; 218 | crdts.forEach(crdt => insertRandom(crdt, numOpsPerCRDT)); 219 | 220 | let chars = []; 221 | const structsWithLines = crdts.map(crdt => crdt.struct); 222 | const structs = structsWithLines.map(struct => { 223 | return [].concat.apply([], struct); 224 | }); 225 | 226 | for (let i = 0; i < structs[0].length; i++) { 227 | structs.forEach(struct => chars.push(struct[i])); 228 | } 229 | 230 | return chars; 231 | } 232 | 233 | function shuffle(a) { 234 | for (let i = a.length - 1; i > 0; i--) { 235 | const j = Math.floor(Math.random() * (i + 1)); 236 | [a[i], a[j]] = [a[j], a[i]]; 237 | } 238 | return a; 239 | } 240 | 241 | function avgIdLength(crdt) { 242 | let numChars = 0; 243 | 244 | const idArray = crdt.struct.map(line => line.map(char => char.position.map(id => id.digit).join(''))); 245 | const digitLengthSum = idArray.reduce((acc, line) => { 246 | return acc + line.reduce((acc, id) => { 247 | numChars++; 248 | return acc + id.length; 249 | }, 0); 250 | }, 0); 251 | 252 | return Math.floor(digitLengthSum / numChars); 253 | } 254 | 255 | function avgPosLength(crdt) { 256 | let numChars = 0; 257 | 258 | const posArray = crdt.struct.map(line => line.map(char => char.position.length)); 259 | const posLengthSum = posArray.reduce((acc, line) => { 260 | return acc + line.reduce((acc, len) => { 261 | numChars++; 262 | return acc + len; 263 | }, 0); 264 | }, 0); 265 | 266 | return Math.floor(posLengthSum / numChars); 267 | } 268 | 269 | function average(time, operations) { 270 | return time / operations; 271 | } 272 | 273 | function addPadding(value, cellSize) { 274 | value = String(value); 275 | 276 | if (value.length > cellSize) { 277 | value = value.slice(0, cellSize); 278 | } 279 | 280 | const padding = ((cellSize - value.length) / 2); 281 | return (' ').repeat(Math.floor(padding)) + value + (' ').repeat(Math.ceil(padding)); 282 | } 283 | 284 | function addRowWithId(operations, crdt, func) { 285 | const totalTime = func(crdt, operations); 286 | const cell1 = addPadding(operations, CELL_1_SIZE); 287 | const cell2 = addPadding(totalTime, CELL_2_SIZE); 288 | const cell3 = addPadding(average(totalTime, operations), CELL_3_SIZE); 289 | const cell4 = addPadding(avgIdLength(crdt), CELL_4_SIZE); 290 | const cell5 = addPadding(avgPosLength(crdt), CELL_5_SIZE); 291 | 292 | return `|${cell1}|${cell2}|${cell3}|${cell4}|${cell5}| 293 | ${'-'.repeat(95)}` 294 | 295 | } 296 | 297 | function addRow(operations, crdt, func) { 298 | const totalTime = func(crdt, operations); 299 | const cell1 = addPadding(operations, CELL_1_SIZE); 300 | const cell2 = addPadding(totalTime, CELL_2_SIZE); 301 | const cell3 = addPadding(average(totalTime, operations), CELL_3_SIZE); 302 | 303 | return `|${cell1}|${cell2}|${cell3}| 304 | ${'-'.repeat(62)}` 305 | } 306 | 307 | function getTimestamp() { 308 | const now = new Date(); 309 | const year = now.getUTCFullYear(); 310 | const month = now.getUTCMonth() + 1; 311 | const date = now.getUTCDate(); 312 | const hours = now.getUTCHours(); 313 | const minutes = now.getUTCMinutes(); 314 | const seconds = now.getUTCSeconds(); 315 | 316 | return `${year}-${month}-${date} ${hours}:${minutes}:${seconds}`; 317 | } 318 | 319 | export { 320 | addRow, 321 | addRowWithId, 322 | insertRandom, 323 | remoteInsertRandom, 324 | insertEnd, 325 | remoteInsertEnd, 326 | insertBeginning, 327 | remoteInsertBeginning, 328 | deleteRandom, 329 | remoteDeleteRandom, 330 | deleteEnd, 331 | remoteDeleteEnd, 332 | deleteBeginning, 333 | remoteDeleteBeginning, 334 | getTimestamp 335 | }; 336 | -------------------------------------------------------------------------------- /performance/comparisons/linearArray/doubleBase/2017-11-17 5:39:6.log: -------------------------------------------------------------------------------- 1 | 2 | #### PERFORMANCE METRICS 3 | Base: 32 | Boundary: 10 | Strategy: minus 4 | ================================================================================================ 5 | 6 | ## RANDOM 7 | --------- 8 | 9 | # LOCAL INSERTIONS 10 | ----------------------------------------------------------------------------------------------- 11 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 12 | | | (in milliseconds) | (in milliseconds) | | Length | 13 | ----------------------------------------------------------------------------------------------- 14 | 15 | | 10 | 0 | 0 | 2 | 1 | 16 | ----------------------------------------------------------------------------------------------- 17 | | 100 | 0 | 0 | 5 | 2 | 18 | ----------------------------------------------------------------------------------------------- 19 | | 1000 | 5 | 0.005 | 8 | 3 | 20 | ----------------------------------------------------------------------------------------------- 21 | 22 | # LOCAL DELETIONS 23 | -------------------------------------------------------------- 24 | | # of Operations | Total Execute Time | Avg. Operation Time | 25 | | | (in milliseconds) | (in milliseconds) | 26 | -------------------------------------------------------------- 27 | | 10 | 0 | 0 | 28 | -------------------------------------------------------------- 29 | | 100 | 0 | 0 | 30 | -------------------------------------------------------------- 31 | | 1000 | 1 | 0.001 | 32 | -------------------------------------------------------------- 33 | 34 | # REMOTE INSERTIONS 35 | -------------------------------------------------------------- 36 | | # of Operations | Total Execute Time | Avg. Operation Time | 37 | | | (in milliseconds) | (in milliseconds) | 38 | -------------------------------------------------------------- 39 | | 10 | 1 | 0.1 | 40 | -------------------------------------------------------------- 41 | | 100 | 0 | 0 | 42 | -------------------------------------------------------------- 43 | | 1000 | 5 | 0.005 | 44 | -------------------------------------------------------------- 45 | 46 | # REMOTE DELETIONS 47 | -------------------------------------------------------------- 48 | | # of Operations | Total Execute Time | Avg. Operation Time | 49 | | | (in milliseconds) | (in milliseconds) | 50 | -------------------------------------------------------------- 51 | | 10 | 0 | 0 | 52 | -------------------------------------------------------------- 53 | | 100 | 0 | 0 | 54 | -------------------------------------------------------------- 55 | | 1000 | 3 | 0.003 | 56 | -------------------------------------------------------------- 57 | 58 | 59 | ## AT THE BEGINNING 60 | ------------------- 61 | 62 | # LOCAL INSERTIONS 63 | ----------------------------------------------------------------------------------------------- 64 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 65 | | | (in milliseconds) | (in milliseconds) | | Length | 66 | ----------------------------------------------------------------------------------------------- 67 | | 10 | 0 | 0 | 2 | 1 | 68 | ----------------------------------------------------------------------------------------------- 69 | | 100 | 0 | 0 | 4 | 3 | 70 | ----------------------------------------------------------------------------------------------- 71 | | 1000 | 4 | 0.004 | 8 | 6 | 72 | ----------------------------------------------------------------------------------------------- 73 | 74 | # LOCAL DELETIONS 75 | -------------------------------------------------------------- 76 | | # of Operations | Total Execute Time | Avg. Operation Time | 77 | | | (in milliseconds) | (in milliseconds) | 78 | -------------------------------------------------------------- 79 | | 10 | 0 | 0 | 80 | -------------------------------------------------------------- 81 | | 100 | 0 | 0 | 82 | -------------------------------------------------------------- 83 | | 1000 | 1 | 0.001 | 84 | -------------------------------------------------------------- 85 | 86 | # REMOTE INSERTIONS 87 | -------------------------------------------------------------- 88 | | # of Operations | Total Execute Time | Avg. Operation Time | 89 | | | (in milliseconds) | (in milliseconds) | 90 | -------------------------------------------------------------- 91 | | 10 | 0 | 0 | 92 | -------------------------------------------------------------- 93 | | 100 | 0 | 0 | 94 | -------------------------------------------------------------- 95 | | 1000 | 1 | 0.001 | 96 | -------------------------------------------------------------- 97 | 98 | # REMOTE DELETIONS 99 | -------------------------------------------------------------- 100 | | # of Operations | Total Execute Time | Avg. Operation Time | 101 | | | (in milliseconds) | (in milliseconds) | 102 | -------------------------------------------------------------- 103 | | 10 | 0 | 0 | 104 | -------------------------------------------------------------- 105 | | 100 | 0 | 0 | 106 | -------------------------------------------------------------- 107 | | 1000 | 1 | 0.001 | 108 | -------------------------------------------------------------- 109 | 110 | 111 | ## AT THE END 112 | ------------- 113 | 114 | # LOCAL INSERTIONS 115 | ----------------------------------------------------------------------------------------------- 116 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 117 | | | (in milliseconds) | (in milliseconds) | | Length | 118 | ----------------------------------------------------------------------------------------------- 119 | | 10 | 0 | 0 | 5 | 2 | 120 | ----------------------------------------------------------------------------------------------- 121 | | 100 | 2 | 0.02 | 106 | 18 | 122 | ----------------------------------------------------------------------------------------------- 123 | | 1000 | 573 | 0.573 | 8068 | 398 | 124 | ----------------------------------------------------------------------------------------------- 125 | 126 | # LOCAL DELETIONS 127 | -------------------------------------------------------------- 128 | | # of Operations | Total Execute Time | Avg. Operation Time | 129 | | | (in milliseconds) | (in milliseconds) | 130 | -------------------------------------------------------------- 131 | | 10 | 0 | 0 | 132 | -------------------------------------------------------------- 133 | | 100 | 0 | 0 | 134 | -------------------------------------------------------------- 135 | | 1000 | 1 | 0.001 | 136 | -------------------------------------------------------------- 137 | 138 | # REMOTE INSERTIONS 139 | -------------------------------------------------------------- 140 | | # of Operations | Total Execute Time | Avg. Operation Time | 141 | | | (in milliseconds) | (in milliseconds) | 142 | -------------------------------------------------------------- 143 | | 10 | 0 | 0 | 144 | -------------------------------------------------------------- 145 | | 100 | 1 | 0.01 | 146 | -------------------------------------------------------------- 147 | | 1000 | 5 | 0.005 | 148 | -------------------------------------------------------------- 149 | 150 | # REMOTE DELETIONS 151 | -------------------------------------------------------------- 152 | | # of Operations | Total Execute Time | Avg. Operation Time | 153 | | | (in milliseconds) | (in milliseconds) | 154 | -------------------------------------------------------------- 155 | | 10 | 0 | 0 | 156 | -------------------------------------------------------------- 157 | | 100 | 0 | 0 | 158 | -------------------------------------------------------------- 159 | | 1000 | 6 | 0.006 | 160 | -------------------------------------------------------------- 161 | -------------------------------------------------------------------------------- /performance/comparisons/linearArray/doubleBase/2017-11-17 5:38:54.log: -------------------------------------------------------------------------------- 1 | 2 | #### PERFORMANCE METRICS 3 | Base: 3200 | Boundary: 3000 | Strategy: minus 4 | ================================================================================================ 5 | 6 | ## RANDOM 7 | --------- 8 | 9 | # LOCAL INSERTIONS 10 | ----------------------------------------------------------------------------------------------- 11 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 12 | | | (in milliseconds) | (in milliseconds) | | Length | 13 | ----------------------------------------------------------------------------------------------- 14 | 15 | | 10 | 1 | 0.1 | 3 | 1 | 16 | ----------------------------------------------------------------------------------------------- 17 | | 100 | 1 | 0.01 | 4 | 1 | 18 | ----------------------------------------------------------------------------------------------- 19 | | 1000 | 8 | 0.008 | 6 | 2 | 20 | ----------------------------------------------------------------------------------------------- 21 | 22 | # LOCAL DELETIONS 23 | -------------------------------------------------------------- 24 | | # of Operations | Total Execute Time | Avg. Operation Time | 25 | | | (in milliseconds) | (in milliseconds) | 26 | -------------------------------------------------------------- 27 | | 10 | 0 | 0 | 28 | -------------------------------------------------------------- 29 | | 100 | 0 | 0 | 30 | -------------------------------------------------------------- 31 | | 1000 | 1 | 0.001 | 32 | -------------------------------------------------------------- 33 | 34 | # REMOTE INSERTIONS 35 | -------------------------------------------------------------- 36 | | # of Operations | Total Execute Time | Avg. Operation Time | 37 | | | (in milliseconds) | (in milliseconds) | 38 | -------------------------------------------------------------- 39 | | 10 | 1 | 0.1 | 40 | -------------------------------------------------------------- 41 | | 100 | 0 | 0 | 42 | -------------------------------------------------------------- 43 | | 1000 | 6 | 0.006 | 44 | -------------------------------------------------------------- 45 | 46 | # REMOTE DELETIONS 47 | -------------------------------------------------------------- 48 | | # of Operations | Total Execute Time | Avg. Operation Time | 49 | | | (in milliseconds) | (in milliseconds) | 50 | -------------------------------------------------------------- 51 | | 10 | 0 | 0 | 52 | -------------------------------------------------------------- 53 | | 100 | 0 | 0 | 54 | -------------------------------------------------------------- 55 | | 1000 | 3 | 0.003 | 56 | -------------------------------------------------------------- 57 | 58 | 59 | ## AT THE BEGINNING 60 | ------------------- 61 | 62 | # LOCAL INSERTIONS 63 | ----------------------------------------------------------------------------------------------- 64 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 65 | | | (in milliseconds) | (in milliseconds) | | Length | 66 | ----------------------------------------------------------------------------------------------- 67 | | 10 | 0 | 0 | 2 | 1 | 68 | ----------------------------------------------------------------------------------------------- 69 | | 100 | 0 | 0 | 6 | 4 | 70 | ----------------------------------------------------------------------------------------------- 71 | | 1000 | 4 | 0.004 | 12 | 7 | 72 | ----------------------------------------------------------------------------------------------- 73 | 74 | # LOCAL DELETIONS 75 | -------------------------------------------------------------- 76 | | # of Operations | Total Execute Time | Avg. Operation Time | 77 | | | (in milliseconds) | (in milliseconds) | 78 | -------------------------------------------------------------- 79 | | 10 | 0 | 0 | 80 | -------------------------------------------------------------- 81 | | 100 | 0 | 0 | 82 | -------------------------------------------------------------- 83 | | 1000 | 1 | 0.001 | 84 | -------------------------------------------------------------- 85 | 86 | # REMOTE INSERTIONS 87 | -------------------------------------------------------------- 88 | | # of Operations | Total Execute Time | Avg. Operation Time | 89 | | | (in milliseconds) | (in milliseconds) | 90 | -------------------------------------------------------------- 91 | | 10 | 0 | 0 | 92 | -------------------------------------------------------------- 93 | | 100 | 0 | 0 | 94 | -------------------------------------------------------------- 95 | | 1000 | 1 | 0.001 | 96 | -------------------------------------------------------------- 97 | 98 | # REMOTE DELETIONS 99 | -------------------------------------------------------------- 100 | | # of Operations | Total Execute Time | Avg. Operation Time | 101 | | | (in milliseconds) | (in milliseconds) | 102 | -------------------------------------------------------------- 103 | | 10 | 0 | 0 | 104 | -------------------------------------------------------------- 105 | | 100 | 1 | 0.01 | 106 | -------------------------------------------------------------- 107 | | 1000 | 0 | 0 | 108 | -------------------------------------------------------------- 109 | 110 | 111 | ## AT THE END 112 | ------------- 113 | 114 | # LOCAL INSERTIONS 115 | ----------------------------------------------------------------------------------------------- 116 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 117 | | | (in milliseconds) | (in milliseconds) | | Length | 118 | ----------------------------------------------------------------------------------------------- 119 | | 10 | 0 | 0 | 5 | 1 | 120 | ----------------------------------------------------------------------------------------------- 121 | | 100 | 0 | 0 | 27 | 5 | 122 | ----------------------------------------------------------------------------------------------- 123 | | 1000 | 257 | 0.257 | 4156 | 211 | 124 | ----------------------------------------------------------------------------------------------- 125 | 126 | # LOCAL DELETIONS 127 | -------------------------------------------------------------- 128 | | # of Operations | Total Execute Time | Avg. Operation Time | 129 | | | (in milliseconds) | (in milliseconds) | 130 | -------------------------------------------------------------- 131 | | 10 | 0 | 0 | 132 | -------------------------------------------------------------- 133 | | 100 | 0 | 0 | 134 | -------------------------------------------------------------- 135 | | 1000 | 0 | 0 | 136 | -------------------------------------------------------------- 137 | 138 | # REMOTE INSERTIONS 139 | -------------------------------------------------------------- 140 | | # of Operations | Total Execute Time | Avg. Operation Time | 141 | | | (in milliseconds) | (in milliseconds) | 142 | -------------------------------------------------------------- 143 | | 10 | 0 | 0 | 144 | -------------------------------------------------------------- 145 | | 100 | 1 | 0.01 | 146 | -------------------------------------------------------------- 147 | | 1000 | 4 | 0.004 | 148 | -------------------------------------------------------------- 149 | 150 | # REMOTE DELETIONS 151 | -------------------------------------------------------------- 152 | | # of Operations | Total Execute Time | Avg. Operation Time | 153 | | | (in milliseconds) | (in milliseconds) | 154 | -------------------------------------------------------------- 155 | | 10 | 0 | 0 | 156 | -------------------------------------------------------------- 157 | | 100 | 0 | 0 | 158 | -------------------------------------------------------------- 159 | | 1000 | 5 | 0.005 | 160 | -------------------------------------------------------------- 161 | -------------------------------------------------------------------------------- /lib/editor.js: -------------------------------------------------------------------------------- 1 | import CRDT from './crdt'; 2 | import RemoteCursor from './remoteCursor'; 3 | 4 | class Editor { 5 | constructor(mde) { 6 | this.controller = null; 7 | this.mde = mde; 8 | this.remoteCursors = {}; 9 | this.customTabBehavior(); 10 | } 11 | 12 | customTabBehavior() { 13 | this.mde.codemirror.setOption("extraKeys", { 14 | Tab: function(codemirror) { 15 | codemirror.replaceSelection("\t"); 16 | } 17 | }); 18 | } 19 | 20 | bindButtons() { 21 | if (this.controller.urlId == 0) { 22 | this.bindUploadButton(); 23 | } else { 24 | this.hideUploadButton(); 25 | } 26 | 27 | this.bindDownloadButton(); 28 | } 29 | 30 | bindDownloadButton() { 31 | const dlButton = document.querySelector('#download'); 32 | 33 | dlButton.onclick = () => { 34 | const textToSave = this.mde.value(); 35 | const textAsBlob = new Blob([textToSave], { type:"text/plain" }); 36 | const textAsURL = window.URL.createObjectURL(textAsBlob); 37 | const fileName = "Conclave-"+Date.now(); 38 | const downloadLink = document.createElement("a"); 39 | 40 | downloadLink.download = fileName; 41 | downloadLink.innerHTML = "Download File"; 42 | downloadLink.href = textAsURL; 43 | downloadLink.onclick = this.afterDownload; 44 | downloadLink.style.display = "none"; 45 | 46 | document.body.appendChild(downloadLink); 47 | downloadLink.click(); 48 | }; 49 | } 50 | 51 | afterDownload(e, doc=document) { 52 | doc.body.removeChild(e.target); 53 | } 54 | 55 | hideUploadButton(doc=document) { 56 | const ulButton = doc.querySelector('#upload'); 57 | const fileInput = doc.querySelector('#file'); 58 | ulButton.style.display = 'none'; 59 | fileInput.style.display = 'none'; 60 | } 61 | 62 | bindUploadButton(doc=document) { 63 | const fileSelect = doc.querySelector('#file'); 64 | fileSelect.onchange = () => { 65 | const file = doc.querySelector("#file").files[0]; 66 | const fileReader = new FileReader(); 67 | fileReader.onload = (e) => { 68 | const fileText = e.target.result; 69 | this.controller.localInsert(fileText, { line: 0, ch: 0 }); 70 | this.replaceText(this.controller.crdt.toText()); 71 | this.hideUploadButton(); 72 | } 73 | fileReader.readAsText(file, "UTF-8"); 74 | } 75 | } 76 | 77 | bindChangeEvent() { 78 | this.mde.codemirror.on("change", (_, changeObj) => { 79 | if (changeObj.origin === "setValue") return; 80 | if (changeObj.origin === "insertText") return; 81 | if (changeObj.origin === "deleteText") return; 82 | 83 | switch(changeObj.origin) { 84 | case 'redo': 85 | case 'undo': 86 | this.processUndoRedo(changeObj); 87 | break; 88 | case '*compose': 89 | case '+input': 90 | // this.processInsert(changeObj); // uncomment this line for palindromes! 91 | case 'paste': 92 | this.processInsert(changeObj); 93 | break; 94 | case '+delete': 95 | case 'cut': 96 | this.processDelete(changeObj); 97 | break; 98 | default: 99 | throw new Error("Unknown operation attempted in editor."); 100 | } 101 | }); 102 | } 103 | 104 | processInsert(changeObj) { 105 | this.processDelete(changeObj); 106 | const chars = this.extractChars(changeObj.text); 107 | const startPos = changeObj.from; 108 | 109 | this.updateRemoteCursorsInsert(chars, changeObj.to); 110 | this.controller.localInsert(chars, startPos); 111 | } 112 | 113 | isEmpty(textArr) { 114 | return textArr.length === 1 && textArr[0].length === 0; 115 | } 116 | 117 | processDelete(changeObj) { 118 | if (this.isEmpty(changeObj.removed)) return; 119 | const startPos = changeObj.from; 120 | const endPos = changeObj.to; 121 | const chars = this.extractChars(changeObj.removed); 122 | 123 | this.updateRemoteCursorsDelete(chars, changeObj.to, changeObj.from); 124 | this.controller.localDelete(startPos, endPos); 125 | } 126 | 127 | processUndoRedo(changeObj) { 128 | if (changeObj.removed[0].length > 0) { 129 | this.processDelete(changeObj); 130 | } else { 131 | this.processInsert(changeObj); 132 | } 133 | } 134 | 135 | extractChars(text) { 136 | if (text[0] === '' && text[1] === '' && text.length === 2) { 137 | return '\n'; 138 | } else { 139 | return text.join("\n"); 140 | } 141 | } 142 | 143 | replaceText(text) { 144 | const cursor = this.mde.codemirror.getCursor(); 145 | this.mde.value(text); 146 | this.mde.codemirror.setCursor(cursor); 147 | } 148 | 149 | insertText(value, positions, siteId) { 150 | const localCursor = this.mde.codemirror.getCursor(); 151 | const delta = this.generateDeltaFromChars(value); 152 | 153 | this.mde.codemirror.replaceRange(value, positions.from, positions.to, 'insertText'); 154 | this.updateRemoteCursorsInsert(positions.to, siteId); 155 | this.updateRemoteCursor(positions.to, siteId, 'insert', value); 156 | 157 | if (localCursor.line > positions.to.line) { 158 | localCursor.line += delta.line 159 | } else if (localCursor.line === positions.to.line && localCursor.ch > positions.to.ch) { 160 | if (delta.line > 0) { 161 | localCursor.line += delta.line 162 | localCursor.ch -= positions.to.ch; 163 | } 164 | 165 | localCursor.ch += delta.ch; 166 | } 167 | 168 | this.mde.codemirror.setCursor(localCursor); 169 | } 170 | 171 | removeCursor(siteId) { 172 | const remoteCursor = this.remoteCursors[siteId]; 173 | 174 | if (remoteCursor) { 175 | remoteCursor.detach(); 176 | 177 | delete this.remoteCursors[siteId]; 178 | } 179 | } 180 | 181 | updateRemoteCursorsInsert(chars, position, siteId) { 182 | const positionDelta = this.generateDeltaFromChars(chars); 183 | 184 | for (const cursorSiteId in this.remoteCursors) { 185 | if (cursorSiteId === siteId) continue; 186 | const remoteCursor = this.remoteCursors[cursorSiteId]; 187 | const newPosition = Object.assign({}, remoteCursor.lastPosition); 188 | 189 | if (newPosition.line > position.line) { 190 | newPosition.line += positionDelta.line; 191 | } else if (newPosition.line === position.line && newPosition.ch > position.ch) { 192 | if (positionDelta.line > 0) { 193 | newPosition.line += positionDelta.line; 194 | newPosition.ch -= position.ch; 195 | } 196 | 197 | newPosition.ch += positionDelta.ch; 198 | } 199 | 200 | remoteCursor.set(newPosition) 201 | } 202 | } 203 | 204 | updateRemoteCursorsDelete(chars, to, from, siteId) { 205 | const positionDelta = this.generateDeltaFromChars(chars); 206 | 207 | for (const cursorSiteId in this.remoteCursors) { 208 | if (cursorSiteId === siteId) continue; 209 | const remoteCursor = this.remoteCursors[cursorSiteId]; 210 | const newPosition = Object.assign({}, remoteCursor.lastPosition); 211 | 212 | if (newPosition.line > to.line) { 213 | newPosition.line -= positionDelta.line; 214 | } else if (newPosition.line === to.line && newPosition.ch > to.ch) { 215 | if (positionDelta.line > 0) { 216 | newPosition.line -= positionDelta.line; 217 | newPosition.ch += from.ch; 218 | } 219 | 220 | newPosition.ch -= positionDelta.ch; 221 | } 222 | 223 | remoteCursor.set(newPosition) 224 | } 225 | } 226 | 227 | updateRemoteCursor(position, siteId, opType, value) { 228 | const remoteCursor = this.remoteCursors[siteId]; 229 | const clonedPosition = Object.assign({}, position); 230 | 231 | if (opType === 'insert') { 232 | if (value === '\n') { 233 | clonedPosition.line++; 234 | clonedPosition.ch = 0 235 | } else { 236 | clonedPosition.ch++; 237 | } 238 | } else { 239 | clonedPosition.ch--; 240 | } 241 | 242 | if (remoteCursor) { 243 | remoteCursor.set(clonedPosition); 244 | } else { 245 | this.remoteCursors[siteId] = new RemoteCursor(this.mde, siteId, clonedPosition); 246 | } 247 | } 248 | 249 | deleteText(value, positions, siteId) { 250 | const localCursor = this.mde.codemirror.getCursor(); 251 | const delta = this.generateDeltaFromChars(value); 252 | 253 | this.mde.codemirror.replaceRange("", positions.from, positions.to, 'deleteText'); 254 | this.updateRemoteCursorsDelete(positions.to, siteId); 255 | this.updateRemoteCursor(positions.to, siteId, 'delete'); 256 | 257 | if (localCursor.line > positions.to.line) { 258 | localCursor.line -= delta.line; 259 | } else if (localCursor.line === positions.to.line && localCursor.ch > positions.to.ch) { 260 | if (delta.line > 0) { 261 | localCursor.line -= delta.line; 262 | localCursor.ch += positions.from.ch; 263 | } 264 | 265 | localCursor.ch -= delta.ch; 266 | } 267 | 268 | this.mde.codemirror.setCursor(localCursor); 269 | } 270 | 271 | findLinearIdx(lineIdx, chIdx) { 272 | const linesOfText = this.controller.crdt.text.split("\n"); 273 | 274 | let index = 0 275 | for (let i = 0; i < lineIdx; i++) { 276 | index += linesOfText[i].length + 1; 277 | } 278 | 279 | return index + chIdx; 280 | } 281 | 282 | generateDeltaFromChars(chars) { 283 | const delta = { line: 0, ch: 0 }; 284 | let counter = 0; 285 | 286 | while (counter < chars.length) { 287 | if (chars[counter] === '\n') { 288 | delta.line++; 289 | delta.ch = 0; 290 | } else { 291 | delta.ch++; 292 | } 293 | 294 | counter++; 295 | } 296 | 297 | return delta; 298 | } 299 | } 300 | 301 | export default Editor; 302 | -------------------------------------------------------------------------------- /performance/comparisons/linearArray/doubleBase/2017-11-17 1:29:26.log: -------------------------------------------------------------------------------- 1 | 2 | #### PERFORMANCE METRICS 3 | Base: 32 | Boundary: 10 | Strategy: plus 4 | ================================================================================================ 5 | 6 | ## RANDOM 7 | --------- 8 | 9 | # LOCAL INSERTIONS 10 | ----------------------------------------------------------------------------------------------- 11 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 12 | | | (in milliseconds) | (in milliseconds) | | Length | 13 | ----------------------------------------------------------------------------------------------- 14 | 15 | | 10 | 1 | 0.1 | 1 | 1 | 16 | ----------------------------------------------------------------------------------------------- 17 | | 100 | 1 | 0.01 | 3 | 2 | 18 | ----------------------------------------------------------------------------------------------- 19 | | 1000 | 5 | 0.005 | 4 | 4 | 20 | ----------------------------------------------------------------------------------------------- 21 | | 10000 | 56 | 0.0056 | 6 | 5 | 22 | ----------------------------------------------------------------------------------------------- 23 | 24 | # LOCAL DELETIONS 25 | -------------------------------------------------------------- 26 | | # of Operations | Total Execute Time | Avg. Operation Time | 27 | | | (in milliseconds) | (in milliseconds) | 28 | -------------------------------------------------------------- 29 | | 10 | 0 | 0 | 30 | -------------------------------------------------------------- 31 | | 100 | 0 | 0 | 32 | -------------------------------------------------------------- 33 | | 1000 | 1 | 0.001 | 34 | -------------------------------------------------------------- 35 | | 10000 | 32 | 0.0032 | 36 | -------------------------------------------------------------- 37 | 38 | # REMOTE INSERTIONS 39 | -------------------------------------------------------------- 40 | | # of Operations | Total Execute Time | Avg. Operation Time | 41 | | | (in milliseconds) | (in milliseconds) | 42 | -------------------------------------------------------------- 43 | | 10 | 0 | 0 | 44 | -------------------------------------------------------------- 45 | | 100 | 0 | 0 | 46 | -------------------------------------------------------------- 47 | | 1000 | 8 | 0.008 | 48 | -------------------------------------------------------------- 49 | | 10000 | 36 | 0.0036 | 50 | -------------------------------------------------------------- 51 | 52 | # REMOTE DELETIONS 53 | -------------------------------------------------------------- 54 | | # of Operations | Total Execute Time | Avg. Operation Time | 55 | | | (in milliseconds) | (in milliseconds) | 56 | -------------------------------------------------------------- 57 | | 10 | 0 | 0 | 58 | -------------------------------------------------------------- 59 | | 100 | 0 | 0 | 60 | -------------------------------------------------------------- 61 | | 1000 | 4 | 0.004 | 62 | -------------------------------------------------------------- 63 | | 10000 | 34 | 0.0034 | 64 | -------------------------------------------------------------- 65 | 66 | 67 | ## AT THE BEGINNING 68 | ------------------- 69 | 70 | # LOCAL INSERTIONS 71 | ----------------------------------------------------------------------------------------------- 72 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 73 | | | (in milliseconds) | (in milliseconds) | | Length | 74 | ----------------------------------------------------------------------------------------------- 75 | | 10 | 0 | 0 | 3 | 3 | 76 | ----------------------------------------------------------------------------------------------- 77 | | 100 | 1 | 0.01 | 17 | 17 | 78 | ----------------------------------------------------------------------------------------------- 79 | | 1000 | 201 | 0.201 | 175 | 175 | 80 | ----------------------------------------------------------------------------------------------- 81 | | 10000 | 525798 | 52.5798 | 1697 | 1697 | 82 | ----------------------------------------------------------------------------------------------- 83 | 84 | # LOCAL DELETIONS 85 | -------------------------------------------------------------- 86 | | # of Operations | Total Execute Time | Avg. Operation Time | 87 | | | (in milliseconds) | (in milliseconds) | 88 | -------------------------------------------------------------- 89 | | 10 | 0 | 0 | 90 | -------------------------------------------------------------- 91 | | 100 | 0 | 0 | 92 | -------------------------------------------------------------- 93 | | 1000 | 0 | 0 | 94 | -------------------------------------------------------------- 95 | | 10000 | 4 | 0.0004 | 96 | -------------------------------------------------------------- 97 | 98 | # REMOTE INSERTIONS 99 | -------------------------------------------------------------- 100 | | # of Operations | Total Execute Time | Avg. Operation Time | 101 | | | (in milliseconds) | (in milliseconds) | 102 | -------------------------------------------------------------- 103 | | 10 | 1 | 0.1 | 104 | -------------------------------------------------------------- 105 | | 100 | 0 | 0 | 106 | -------------------------------------------------------------- 107 | | 1000 | 4 | 0.004 | 108 | -------------------------------------------------------------- 109 | | 10000 | 25 | 0.0025 | 110 | -------------------------------------------------------------- 111 | 112 | # REMOTE DELETIONS 113 | -------------------------------------------------------------- 114 | | # of Operations | Total Execute Time | Avg. Operation Time | 115 | | | (in milliseconds) | (in milliseconds) | 116 | -------------------------------------------------------------- 117 | | 10 | 0 | 0 | 118 | -------------------------------------------------------------- 119 | | 100 | 1 | 0.01 | 120 | -------------------------------------------------------------- 121 | | 1000 | 2 | 0.002 | 122 | -------------------------------------------------------------- 123 | | 10000 | 27 | 0.0027 | 124 | -------------------------------------------------------------- 125 | 126 | 127 | ## AT THE END 128 | ------------- 129 | 130 | # LOCAL INSERTIONS 131 | ----------------------------------------------------------------------------------------------- 132 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 133 | | | (in milliseconds) | (in milliseconds) | | Length | 134 | ----------------------------------------------------------------------------------------------- 135 | | 10 | 0 | 0 | 2 | 1 | 136 | ----------------------------------------------------------------------------------------------- 137 | | 100 | 0 | 0 | 7 | 3 | 138 | ----------------------------------------------------------------------------------------------- 139 | | 1000 | 2 | 0.002 | 18 | 6 | 140 | ----------------------------------------------------------------------------------------------- 141 | | 10000 | 50 | 0.005 | 32 | 9 | 142 | ----------------------------------------------------------------------------------------------- 143 | 144 | # LOCAL DELETIONS 145 | -------------------------------------------------------------- 146 | | # of Operations | Total Execute Time | Avg. Operation Time | 147 | | | (in milliseconds) | (in milliseconds) | 148 | -------------------------------------------------------------- 149 | | 10 | 0 | 0 | 150 | -------------------------------------------------------------- 151 | | 100 | 0 | 0 | 152 | -------------------------------------------------------------- 153 | | 1000 | 0 | 0 | 154 | -------------------------------------------------------------- 155 | | 10000 | 3 | 0.0003 | 156 | -------------------------------------------------------------- 157 | 158 | # REMOTE INSERTIONS 159 | -------------------------------------------------------------- 160 | | # of Operations | Total Execute Time | Avg. Operation Time | 161 | | | (in milliseconds) | (in milliseconds) | 162 | -------------------------------------------------------------- 163 | | 10 | 0 | 0 | 164 | -------------------------------------------------------------- 165 | | 100 | 0 | 0 | 166 | -------------------------------------------------------------- 167 | | 1000 | 1 | 0.001 | 168 | -------------------------------------------------------------- 169 | | 10000 | 18 | 0.0018 | 170 | -------------------------------------------------------------- 171 | 172 | # REMOTE DELETIONS 173 | -------------------------------------------------------------- 174 | | # of Operations | Total Execute Time | Avg. Operation Time | 175 | | | (in milliseconds) | (in milliseconds) | 176 | -------------------------------------------------------------- 177 | | 10 | 0 | 0 | 178 | -------------------------------------------------------------- 179 | | 100 | 0 | 0 | 180 | -------------------------------------------------------------- 181 | | 1000 | 2 | 0.002 | 182 | -------------------------------------------------------------- 183 | | 10000 | 25 | 0.0025 | 184 | -------------------------------------------------------------- 185 | -------------------------------------------------------------------------------- /performance/comparisons/linearArray/tripleBase/2017-11-17 1:44:28.log: -------------------------------------------------------------------------------- 1 | 2 | #### PERFORMANCE METRICS 3 | Base: 32 | Boundary: 10 | Strategy: plus 4 | ================================================================================================ 5 | 6 | ## RANDOM 7 | --------- 8 | 9 | # LOCAL INSERTIONS 10 | ----------------------------------------------------------------------------------------------- 11 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 12 | | | (in milliseconds) | (in milliseconds) | | Length | 13 | ----------------------------------------------------------------------------------------------- 14 | 15 | | 10 | 1 | 0.1 | 2 | 2 | 16 | ----------------------------------------------------------------------------------------------- 17 | | 100 | 1 | 0.01 | 2 | 2 | 18 | ----------------------------------------------------------------------------------------------- 19 | | 1000 | 7 | 0.007 | 5 | 4 | 20 | ----------------------------------------------------------------------------------------------- 21 | | 10000 | 60 | 0.006 | 6 | 4 | 22 | ----------------------------------------------------------------------------------------------- 23 | 24 | # LOCAL DELETIONS 25 | -------------------------------------------------------------- 26 | | # of Operations | Total Execute Time | Avg. Operation Time | 27 | | | (in milliseconds) | (in milliseconds) | 28 | -------------------------------------------------------------- 29 | | 10 | 0 | 0 | 30 | -------------------------------------------------------------- 31 | | 100 | 0 | 0 | 32 | -------------------------------------------------------------- 33 | | 1000 | 1 | 0.001 | 34 | -------------------------------------------------------------- 35 | | 10000 | 13 | 0.0013 | 36 | -------------------------------------------------------------- 37 | 38 | # REMOTE INSERTIONS 39 | -------------------------------------------------------------- 40 | | # of Operations | Total Execute Time | Avg. Operation Time | 41 | | | (in milliseconds) | (in milliseconds) | 42 | -------------------------------------------------------------- 43 | | 10 | 0 | 0 | 44 | -------------------------------------------------------------- 45 | | 100 | 0 | 0 | 46 | -------------------------------------------------------------- 47 | | 1000 | 8 | 0.008 | 48 | -------------------------------------------------------------- 49 | | 10000 | 41 | 0.0041 | 50 | -------------------------------------------------------------- 51 | 52 | # REMOTE DELETIONS 53 | -------------------------------------------------------------- 54 | | # of Operations | Total Execute Time | Avg. Operation Time | 55 | | | (in milliseconds) | (in milliseconds) | 56 | -------------------------------------------------------------- 57 | | 10 | 0 | 0 | 58 | -------------------------------------------------------------- 59 | | 100 | 0 | 0 | 60 | -------------------------------------------------------------- 61 | | 1000 | 3 | 0.003 | 62 | -------------------------------------------------------------- 63 | | 10000 | 35 | 0.0035 | 64 | -------------------------------------------------------------- 65 | 66 | 67 | ## AT THE BEGINNING 68 | ------------------- 69 | 70 | # LOCAL INSERTIONS 71 | ----------------------------------------------------------------------------------------------- 72 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 73 | | | (in milliseconds) | (in milliseconds) | | Length | 74 | ----------------------------------------------------------------------------------------------- 75 | | 10 | 0 | 0 | 3 | 3 | 76 | ----------------------------------------------------------------------------------------------- 77 | | 100 | 3 | 0.03 | 19 | 18 | 78 | ----------------------------------------------------------------------------------------------- 79 | | 1000 | 199 | 0.199 | 175 | 175 | 80 | ----------------------------------------------------------------------------------------------- 81 | | 10000 | 523694 | 52.3694 | 1689 | 1689 | 82 | ----------------------------------------------------------------------------------------------- 83 | 84 | # LOCAL DELETIONS 85 | -------------------------------------------------------------- 86 | | # of Operations | Total Execute Time | Avg. Operation Time | 87 | | | (in milliseconds) | (in milliseconds) | 88 | -------------------------------------------------------------- 89 | | 10 | 0 | 0 | 90 | -------------------------------------------------------------- 91 | | 100 | 0 | 0 | 92 | -------------------------------------------------------------- 93 | | 1000 | 1 | 0.001 | 94 | -------------------------------------------------------------- 95 | | 10000 | 4 | 0.0004 | 96 | -------------------------------------------------------------- 97 | 98 | # REMOTE INSERTIONS 99 | -------------------------------------------------------------- 100 | | # of Operations | Total Execute Time | Avg. Operation Time | 101 | | | (in milliseconds) | (in milliseconds) | 102 | -------------------------------------------------------------- 103 | | 10 | 0 | 0 | 104 | -------------------------------------------------------------- 105 | | 100 | 1 | 0.01 | 106 | -------------------------------------------------------------- 107 | | 1000 | 10 | 0.01 | 108 | -------------------------------------------------------------- 109 | | 10000 | 25 | 0.0025 | 110 | -------------------------------------------------------------- 111 | 112 | # REMOTE DELETIONS 113 | -------------------------------------------------------------- 114 | | # of Operations | Total Execute Time | Avg. Operation Time | 115 | | | (in milliseconds) | (in milliseconds) | 116 | -------------------------------------------------------------- 117 | | 10 | 1 | 0.1 | 118 | -------------------------------------------------------------- 119 | | 100 | 0 | 0 | 120 | -------------------------------------------------------------- 121 | | 1000 | 3 | 0.003 | 122 | -------------------------------------------------------------- 123 | | 10000 | 22 | 0.0022 | 124 | -------------------------------------------------------------- 125 | 126 | 127 | ## AT THE END 128 | ------------- 129 | 130 | # LOCAL INSERTIONS 131 | ----------------------------------------------------------------------------------------------- 132 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 133 | | | (in milliseconds) | (in milliseconds) | | Length | 134 | ----------------------------------------------------------------------------------------------- 135 | | 10 | 0 | 0 | 2 | 1 | 136 | ----------------------------------------------------------------------------------------------- 137 | | 100 | 0 | 0 | 6 | 3 | 138 | ----------------------------------------------------------------------------------------------- 139 | | 1000 | 13 | 0.013 | 13 | 4 | 140 | ----------------------------------------------------------------------------------------------- 141 | | 10000 | 28 | 0.0028 | 22 | 7 | 142 | ----------------------------------------------------------------------------------------------- 143 | 144 | # LOCAL DELETIONS 145 | -------------------------------------------------------------- 146 | | # of Operations | Total Execute Time | Avg. Operation Time | 147 | | | (in milliseconds) | (in milliseconds) | 148 | -------------------------------------------------------------- 149 | | 10 | 0 | 0 | 150 | -------------------------------------------------------------- 151 | | 100 | 0 | 0 | 152 | -------------------------------------------------------------- 153 | | 1000 | 1 | 0.001 | 154 | -------------------------------------------------------------- 155 | | 10000 | 2 | 0.0002 | 156 | -------------------------------------------------------------- 157 | 158 | # REMOTE INSERTIONS 159 | -------------------------------------------------------------- 160 | | # of Operations | Total Execute Time | Avg. Operation Time | 161 | | | (in milliseconds) | (in milliseconds) | 162 | -------------------------------------------------------------- 163 | | 10 | 0 | 0 | 164 | -------------------------------------------------------------- 165 | | 100 | 0 | 0 | 166 | -------------------------------------------------------------- 167 | | 1000 | 1 | 0.001 | 168 | -------------------------------------------------------------- 169 | | 10000 | 15 | 0.0015 | 170 | -------------------------------------------------------------- 171 | 172 | # REMOTE DELETIONS 173 | -------------------------------------------------------------- 174 | | # of Operations | Total Execute Time | Avg. Operation Time | 175 | | | (in milliseconds) | (in milliseconds) | 176 | -------------------------------------------------------------- 177 | | 10 | 0 | 0 | 178 | -------------------------------------------------------------- 179 | | 100 | 0 | 0 | 180 | -------------------------------------------------------------- 181 | | 1000 | 1 | 0.001 | 182 | -------------------------------------------------------------- 183 | | 10000 | 16 | 0.0016 | 184 | -------------------------------------------------------------- 185 | -------------------------------------------------------------------------------- /performance/comparisons/linearArray/constantBase/2017-11-17 2:26:13.log: -------------------------------------------------------------------------------- 1 | 2 | #### PERFORMANCE METRICS 3 | Base: 32 | Boundary: 10 | Strategy: plus 4 | ================================================================================================ 5 | 6 | ## RANDOM 7 | --------- 8 | 9 | # LOCAL INSERTIONS 10 | ----------------------------------------------------------------------------------------------- 11 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 12 | | | (in milliseconds) | (in milliseconds) | | Length | 13 | ----------------------------------------------------------------------------------------------- 14 | 15 | | 10 | 0 | 0 | 1 | 1 | 16 | ----------------------------------------------------------------------------------------------- 17 | | 100 | 0 | 0 | 3 | 2 | 18 | ----------------------------------------------------------------------------------------------- 19 | | 1000 | 6 | 0.006 | 4 | 4 | 20 | ----------------------------------------------------------------------------------------------- 21 | | 10000 | 58 | 0.0058 | 5 | 4 | 22 | ----------------------------------------------------------------------------------------------- 23 | 24 | # LOCAL DELETIONS 25 | -------------------------------------------------------------- 26 | | # of Operations | Total Execute Time | Avg. Operation Time | 27 | | | (in milliseconds) | (in milliseconds) | 28 | -------------------------------------------------------------- 29 | | 10 | 0 | 0 | 30 | -------------------------------------------------------------- 31 | | 100 | 0 | 0 | 32 | -------------------------------------------------------------- 33 | | 1000 | 0 | 0 | 34 | -------------------------------------------------------------- 35 | | 10000 | 13 | 0.0013 | 36 | -------------------------------------------------------------- 37 | 38 | # REMOTE INSERTIONS 39 | -------------------------------------------------------------- 40 | | # of Operations | Total Execute Time | Avg. Operation Time | 41 | | | (in milliseconds) | (in milliseconds) | 42 | -------------------------------------------------------------- 43 | | 10 | 0 | 0 | 44 | -------------------------------------------------------------- 45 | | 100 | 1 | 0.01 | 46 | -------------------------------------------------------------- 47 | | 1000 | 6 | 0.006 | 48 | -------------------------------------------------------------- 49 | | 10000 | 29 | 0.0029 | 50 | -------------------------------------------------------------- 51 | 52 | # REMOTE DELETIONS 53 | -------------------------------------------------------------- 54 | | # of Operations | Total Execute Time | Avg. Operation Time | 55 | | | (in milliseconds) | (in milliseconds) | 56 | -------------------------------------------------------------- 57 | | 10 | 1 | 0.1 | 58 | -------------------------------------------------------------- 59 | | 100 | 0 | 0 | 60 | -------------------------------------------------------------- 61 | | 1000 | 3 | 0.003 | 62 | -------------------------------------------------------------- 63 | | 10000 | 27 | 0.0027 | 64 | -------------------------------------------------------------- 65 | 66 | 67 | ## AT THE BEGINNING 68 | ------------------- 69 | 70 | # LOCAL INSERTIONS 71 | ----------------------------------------------------------------------------------------------- 72 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 73 | | | (in milliseconds) | (in milliseconds) | | Length | 74 | ----------------------------------------------------------------------------------------------- 75 | | 10 | 0 | 0 | 2 | 2 | 76 | ----------------------------------------------------------------------------------------------- 77 | | 100 | 1 | 0.01 | 17 | 17 | 78 | ----------------------------------------------------------------------------------------------- 79 | | 1000 | 167 | 0.167 | 172 | 172 | 80 | ----------------------------------------------------------------------------------------------- 81 | | 10000 | 387842 | 38.7842 | 1703 | 1703 | 82 | ----------------------------------------------------------------------------------------------- 83 | 84 | # LOCAL DELETIONS 85 | -------------------------------------------------------------- 86 | | # of Operations | Total Execute Time | Avg. Operation Time | 87 | | | (in milliseconds) | (in milliseconds) | 88 | -------------------------------------------------------------- 89 | | 10 | 0 | 0 | 90 | -------------------------------------------------------------- 91 | | 100 | 0 | 0 | 92 | -------------------------------------------------------------- 93 | | 1000 | 0 | 0 | 94 | -------------------------------------------------------------- 95 | | 10000 | 9 | 0.0009 | 96 | -------------------------------------------------------------- 97 | 98 | # REMOTE INSERTIONS 99 | -------------------------------------------------------------- 100 | | # of Operations | Total Execute Time | Avg. Operation Time | 101 | | | (in milliseconds) | (in milliseconds) | 102 | -------------------------------------------------------------- 103 | | 10 | 0 | 0 | 104 | -------------------------------------------------------------- 105 | | 100 | 0 | 0 | 106 | -------------------------------------------------------------- 107 | | 1000 | 0 | 0 | 108 | -------------------------------------------------------------- 109 | | 10000 | 27 | 0.0027 | 110 | -------------------------------------------------------------- 111 | 112 | # REMOTE DELETIONS 113 | -------------------------------------------------------------- 114 | | # of Operations | Total Execute Time | Avg. Operation Time | 115 | | | (in milliseconds) | (in milliseconds) | 116 | -------------------------------------------------------------- 117 | | 10 | 0 | 0 | 118 | -------------------------------------------------------------- 119 | | 100 | 0 | 0 | 120 | -------------------------------------------------------------- 121 | | 1000 | 1 | 0.001 | 122 | -------------------------------------------------------------- 123 | | 10000 | 29 | 0.0029 | 124 | -------------------------------------------------------------- 125 | 126 | 127 | ## AT THE END 128 | ------------- 129 | 130 | # LOCAL INSERTIONS 131 | ----------------------------------------------------------------------------------------------- 132 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 133 | | | (in milliseconds) | (in milliseconds) | | Length | 134 | ----------------------------------------------------------------------------------------------- 135 | | 10 | 0 | 0 | 2 | 1 | 136 | ----------------------------------------------------------------------------------------------- 137 | | 100 | 0 | 0 | 15 | 7 | 138 | ----------------------------------------------------------------------------------------------- 139 | | 1000 | 49 | 0.049 | 141 | 70 | 140 | ----------------------------------------------------------------------------------------------- 141 | | 10000 | 29091 | 2.9091 | 1455 | 727 | 142 | ----------------------------------------------------------------------------------------------- 143 | 144 | # LOCAL DELETIONS 145 | -------------------------------------------------------------- 146 | | # of Operations | Total Execute Time | Avg. Operation Time | 147 | | | (in milliseconds) | (in milliseconds) | 148 | -------------------------------------------------------------- 149 | | 10 | 0 | 0 | 150 | -------------------------------------------------------------- 151 | | 100 | 0 | 0 | 152 | -------------------------------------------------------------- 153 | | 1000 | 0 | 0 | 154 | -------------------------------------------------------------- 155 | | 10000 | 2 | 0.0002 | 156 | -------------------------------------------------------------- 157 | 158 | # REMOTE INSERTIONS 159 | -------------------------------------------------------------- 160 | | # of Operations | Total Execute Time | Avg. Operation Time | 161 | | | (in milliseconds) | (in milliseconds) | 162 | -------------------------------------------------------------- 163 | | 10 | 0 | 0 | 164 | -------------------------------------------------------------- 165 | | 100 | 0 | 0 | 166 | -------------------------------------------------------------- 167 | | 1000 | 1 | 0.001 | 168 | -------------------------------------------------------------- 169 | | 10000 | 14 | 0.0014 | 170 | -------------------------------------------------------------- 171 | 172 | # REMOTE DELETIONS 173 | -------------------------------------------------------------- 174 | | # of Operations | Total Execute Time | Avg. Operation Time | 175 | | | (in milliseconds) | (in milliseconds) | 176 | -------------------------------------------------------------- 177 | | 10 | 0 | 0 | 178 | -------------------------------------------------------------- 179 | | 100 | 0 | 0 | 180 | -------------------------------------------------------------- 181 | | 1000 | 1 | 0.001 | 182 | -------------------------------------------------------------- 183 | | 10000 | 15 | 0.0015 | 184 | -------------------------------------------------------------- 185 | -------------------------------------------------------------------------------- /performance/comparisons/linearArray/constantBase/2017-11-17 5:19:28.log: -------------------------------------------------------------------------------- 1 | 2 | #### PERFORMANCE METRICS 3 | Base: 32 | Boundary: 10 | Strategy: random 4 | ================================================================================================ 5 | 6 | ## RANDOM 7 | --------- 8 | 9 | # LOCAL INSERTIONS 10 | ----------------------------------------------------------------------------------------------- 11 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 12 | | | (in milliseconds) | (in milliseconds) | | Length | 13 | ----------------------------------------------------------------------------------------------- 14 | 15 | | 10 | 0 | 0 | 1 | 1 | 16 | ----------------------------------------------------------------------------------------------- 17 | | 100 | 0 | 0 | 4 | 2 | 18 | ----------------------------------------------------------------------------------------------- 19 | | 1000 | 7 | 0.007 | 7 | 4 | 20 | ----------------------------------------------------------------------------------------------- 21 | | 10000 | 69 | 0.0069 | 9 | 5 | 22 | ----------------------------------------------------------------------------------------------- 23 | 24 | # LOCAL DELETIONS 25 | -------------------------------------------------------------- 26 | | # of Operations | Total Execute Time | Avg. Operation Time | 27 | | | (in milliseconds) | (in milliseconds) | 28 | -------------------------------------------------------------- 29 | | 10 | 0 | 0 | 30 | -------------------------------------------------------------- 31 | | 100 | 0 | 0 | 32 | -------------------------------------------------------------- 33 | | 1000 | 0 | 0 | 34 | -------------------------------------------------------------- 35 | | 10000 | 13 | 0.0013 | 36 | -------------------------------------------------------------- 37 | 38 | # REMOTE INSERTIONS 39 | -------------------------------------------------------------- 40 | | # of Operations | Total Execute Time | Avg. Operation Time | 41 | | | (in milliseconds) | (in milliseconds) | 42 | -------------------------------------------------------------- 43 | | 10 | 0 | 0 | 44 | -------------------------------------------------------------- 45 | | 100 | 0 | 0 | 46 | -------------------------------------------------------------- 47 | | 1000 | 7 | 0.007 | 48 | -------------------------------------------------------------- 49 | | 10000 | 34 | 0.0034 | 50 | -------------------------------------------------------------- 51 | 52 | # REMOTE DELETIONS 53 | -------------------------------------------------------------- 54 | | # of Operations | Total Execute Time | Avg. Operation Time | 55 | | | (in milliseconds) | (in milliseconds) | 56 | -------------------------------------------------------------- 57 | | 10 | 0 | 0 | 58 | -------------------------------------------------------------- 59 | | 100 | 0 | 0 | 60 | -------------------------------------------------------------- 61 | | 1000 | 5 | 0.005 | 62 | -------------------------------------------------------------- 63 | | 10000 | 31 | 0.0031 | 64 | -------------------------------------------------------------- 65 | 66 | 67 | ## AT THE BEGINNING 68 | ------------------- 69 | 70 | # LOCAL INSERTIONS 71 | ----------------------------------------------------------------------------------------------- 72 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 73 | | | (in milliseconds) | (in milliseconds) | | Length | 74 | ----------------------------------------------------------------------------------------------- 75 | | 10 | 0 | 0 | 1 | 1 | 76 | ----------------------------------------------------------------------------------------------- 77 | | 100 | 1 | 0.01 | 10 | 10 | 78 | ----------------------------------------------------------------------------------------------- 79 | | 1000 | 93 | 0.093 | 106 | 106 | 80 | ----------------------------------------------------------------------------------------------- 81 | | 10000 | 57860 | 5.786 | 1027 | 1027 | 82 | ----------------------------------------------------------------------------------------------- 83 | 84 | # LOCAL DELETIONS 85 | -------------------------------------------------------------- 86 | | # of Operations | Total Execute Time | Avg. Operation Time | 87 | | | (in milliseconds) | (in milliseconds) | 88 | -------------------------------------------------------------- 89 | | 10 | 0 | 0 | 90 | -------------------------------------------------------------- 91 | | 100 | 0 | 0 | 92 | -------------------------------------------------------------- 93 | | 1000 | 0 | 0 | 94 | -------------------------------------------------------------- 95 | | 10000 | 4 | 0.0004 | 96 | -------------------------------------------------------------- 97 | 98 | # REMOTE INSERTIONS 99 | -------------------------------------------------------------- 100 | | # of Operations | Total Execute Time | Avg. Operation Time | 101 | | | (in milliseconds) | (in milliseconds) | 102 | -------------------------------------------------------------- 103 | | 10 | 0 | 0 | 104 | -------------------------------------------------------------- 105 | | 100 | 0 | 0 | 106 | -------------------------------------------------------------- 107 | | 1000 | 0 | 0 | 108 | -------------------------------------------------------------- 109 | | 10000 | 19 | 0.0019 | 110 | -------------------------------------------------------------- 111 | 112 | # REMOTE DELETIONS 113 | -------------------------------------------------------------- 114 | | # of Operations | Total Execute Time | Avg. Operation Time | 115 | | | (in milliseconds) | (in milliseconds) | 116 | -------------------------------------------------------------- 117 | | 10 | 0 | 0 | 118 | -------------------------------------------------------------- 119 | | 100 | 0 | 0 | 120 | -------------------------------------------------------------- 121 | | 1000 | 1 | 0.001 | 122 | -------------------------------------------------------------- 123 | | 10000 | 15 | 0.0015 | 124 | -------------------------------------------------------------- 125 | 126 | 127 | ## AT THE END 128 | ------------- 129 | 130 | # LOCAL INSERTIONS 131 | ----------------------------------------------------------------------------------------------- 132 | | # of Operations | Total Execute Time | Avg. Operation Time | Avg. ID Length | Avg. Position | 133 | | | (in milliseconds) | (in milliseconds) | | Length | 134 | ----------------------------------------------------------------------------------------------- 135 | | 10 | 0 | 0 | 4 | 2 | 136 | ----------------------------------------------------------------------------------------------- 137 | | 100 | 0 | 0 | 20 | 10 | 138 | ----------------------------------------------------------------------------------------------- 139 | | 1000 | 58 | 0.058 | 196 | 98 | 140 | ----------------------------------------------------------------------------------------------- 141 | | 10000 | 51955 | 5.1955 | 2038 | 1019 | 142 | ----------------------------------------------------------------------------------------------- 143 | 144 | # LOCAL DELETIONS 145 | -------------------------------------------------------------- 146 | | # of Operations | Total Execute Time | Avg. Operation Time | 147 | | | (in milliseconds) | (in milliseconds) | 148 | -------------------------------------------------------------- 149 | | 10 | 0 | 0 | 150 | -------------------------------------------------------------- 151 | | 100 | 0 | 0 | 152 | -------------------------------------------------------------- 153 | | 1000 | 0 | 0 | 154 | -------------------------------------------------------------- 155 | | 10000 | 2 | 0.0002 | 156 | -------------------------------------------------------------- 157 | 158 | # REMOTE INSERTIONS 159 | -------------------------------------------------------------- 160 | | # of Operations | Total Execute Time | Avg. Operation Time | 161 | | | (in milliseconds) | (in milliseconds) | 162 | -------------------------------------------------------------- 163 | | 10 | 0 | 0 | 164 | -------------------------------------------------------------- 165 | | 100 | 0 | 0 | 166 | -------------------------------------------------------------- 167 | | 1000 | 1 | 0.001 | 168 | -------------------------------------------------------------- 169 | | 10000 | 16 | 0.0016 | 170 | -------------------------------------------------------------- 171 | 172 | # REMOTE DELETIONS 173 | -------------------------------------------------------------- 174 | | # of Operations | Total Execute Time | Avg. Operation Time | 175 | | | (in milliseconds) | (in milliseconds) | 176 | -------------------------------------------------------------- 177 | | 10 | 0 | 0 | 178 | -------------------------------------------------------------- 179 | | 100 | 0 | 0 | 180 | -------------------------------------------------------------- 181 | | 1000 | 1 | 0.001 | 182 | -------------------------------------------------------------- 183 | | 10000 | 17 | 0.0017 | 184 | -------------------------------------------------------------- 185 | --------------------------------------------------------------------------------