From af85c83877258ec64e3d9b6c4af24feb51d5d32b Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 17 Apr 2015 11:22:26 +0100 Subject: [PATCH 1/3] Buffer updates when only a single user is editing a document Add in 5 second delay between flushing updates when only a single user is editing a document. As soon as an update is received from another user we switch to sending updates immediately again so there is no latency between collaborators. The logic applies to individual docs, so two users can be editing different docs and will still buffer updates since they will not affect each other. --- services/web/app/views/project/editor.jade | 2 +- services/web/public/coffee/ide/editor/Document.coffee | 7 +++++++ .../web/public/coffee/ide/editor/EditorManager.coffee | 3 +++ .../web/public/coffee/ide/editor/ShareJsDoc.coffee | 6 ++++++ .../coffee/ide/editor/sharejs/vendor/client/doc.coffee | 10 +++++++++- .../coffee/ide/pdf/controllers/PdfController.coffee | 2 ++ 6 files changed, 28 insertions(+), 2 deletions(-) diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.jade index 303bf9afb0..23abd96759 100644 --- a/services/web/app/views/project/editor.jade +++ b/services/web/app/views/project/editor.jade @@ -29,7 +29,7 @@ block content strong #{translate("reconnecting")}... .div(ng-controller="SavingNotificationController") - .alert.alert-warning.small( ng-repeat="(doc_id, state) in docSavingStatus" ng-if="state.unsavedSeconds > 3") #{translate("saving_notification_with_seconds", {docname:"{{ state.doc.name }}", seconds:"{{ state.unsavedSeconds }}"})} + .alert.alert-warning.small( ng-repeat="(doc_id, state) in docSavingStatus" ng-if="state.unsavedSeconds > 8") #{translate("saving_notification_with_seconds", {docname:"{{ state.doc.name }}", seconds:"{{ state.unsavedSeconds }}"})} include ./editor/left-menu diff --git a/services/web/public/coffee/ide/editor/Document.coffee b/services/web/public/coffee/ide/editor/Document.coffee index deb8f5047c..561c51b133 100644 --- a/services/web/public/coffee/ide/editor/Document.coffee +++ b/services/web/public/coffee/ide/editor/Document.coffee @@ -14,6 +14,10 @@ define [ return true if doc.hasBufferedOps() return false + @flushAll: () -> + for doc_id, doc of @openDocs + doc.flush() + constructor: (@ide, @doc_id) -> @connected = @ide.socket.socket.connected @joined = false @@ -109,6 +113,9 @@ define [ else @_leaveDoc(callback) + flush: () -> + @doc?.flushPendingOps() + pollSavedStatus: () -> # returns false if doc has ops waiting to be acknowledged or # sent that haven't changed since the last time we checked. diff --git a/services/web/public/coffee/ide/editor/EditorManager.coffee b/services/web/public/coffee/ide/editor/EditorManager.coffee index 38927806d6..47f6a15fe5 100644 --- a/services/web/public/coffee/ide/editor/EditorManager.coffee +++ b/services/web/public/coffee/ide/editor/EditorManager.coffee @@ -28,6 +28,9 @@ define [ initialized = true @autoOpenDoc() + @$scope.$on "flush-changes", () => + Document.flushAll() + autoOpenDoc: () -> open_doc_id = @ide.localStorage("doc.open_id.#{@$scope.project_id}") or diff --git a/services/web/public/coffee/ide/editor/ShareJsDoc.coffee b/services/web/public/coffee/ide/editor/ShareJsDoc.coffee index 0894bbff7e..f4a0d83a88 100644 --- a/services/web/public/coffee/ide/editor/ShareJsDoc.coffee +++ b/services/web/public/coffee/ide/editor/ShareJsDoc.coffee @@ -2,6 +2,8 @@ define [ "utils/EventEmitter" "libs/sharejs" ], (EventEmitter, ShareJs) -> + SINGLE_USER_FLUSH_DELAY = 5000 #ms + class ShareJsDoc extends EventEmitter constructor: (@doc_id, docLines, version, @socket) -> # Dencode any binary bits of data @@ -33,11 +35,15 @@ define [ @_doc = new ShareJs.Doc @connection, @doc_id, type: @type + @_doc.setFlushDelay(SINGLE_USER_FLUSH_DELAY) @_doc.on "change", () => @trigger "change" @_doc.on "acknowledge", () => @trigger "acknowledge" @_doc.on "remoteop", () => + # As soon as we're working with a collaborator, start sending + # ops as quickly as possible for low latency. + @_doc.setFlushDelay(0) @trigger "remoteop" @_bindToDocChanges(@_doc) diff --git a/services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee index a8cb7be3c1..c74adad50e 100644 --- a/services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee +++ b/services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee @@ -247,6 +247,9 @@ class Doc # Only one op can be in-flight at a time, so if an op is already on its way then # this method does nothing. flush: => + delete @flushTimeout + #console.log "CALLED FLUSH" + return unless @connection.state == 'ok' and @inflightOp == null and @pendingOp != null # Rotate null -> pending -> inflight @@ -256,6 +259,7 @@ class Doc @pendingOp = null @pendingCallbacks = [] + #console.log "SENDING OP TO SERVER", @inflightOp, @version @connection.send {doc:@name, op:@inflightOp, v:@version} # Submit an op to the server. The op maybe held for a little while before being sent, as only one @@ -277,7 +281,11 @@ class Doc # A timeout is used so if the user sends multiple ops at the same time, they'll be composed # & sent together. - setTimeout @flush, 0 + if !@flushTimeout? + @flushTimeout = setTimeout @flush, @_flushDelay || 0 + + setFlushDelay: (delay) => + @_flushDelay = delay shout: (msg) => # Meta ops don't have to queue, they can go direct. Good/bad idea? diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index f7e4f91082..3aaf6e7600 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -123,6 +123,8 @@ define [ return if $scope.pdf.compiling $scope.pdf.compiling = true + ide.$scope.$broadcast("flush-changes") + if !options.isAutoCompile compileCount++ if compileCount == 1 From 8a32ca1b64bfe2eba19f42e3902436a093e98ab8 Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 17 Apr 2015 16:45:17 +0100 Subject: [PATCH 2/3] Reduce buffer delay to 1 second --- services/web/public/coffee/ide/editor/ShareJsDoc.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/public/coffee/ide/editor/ShareJsDoc.coffee b/services/web/public/coffee/ide/editor/ShareJsDoc.coffee index f4a0d83a88..564666e3dc 100644 --- a/services/web/public/coffee/ide/editor/ShareJsDoc.coffee +++ b/services/web/public/coffee/ide/editor/ShareJsDoc.coffee @@ -2,7 +2,7 @@ define [ "utils/EventEmitter" "libs/sharejs" ], (EventEmitter, ShareJs) -> - SINGLE_USER_FLUSH_DELAY = 5000 #ms + SINGLE_USER_FLUSH_DELAY = 1000 #ms class ShareJsDoc extends EventEmitter constructor: (@doc_id, docLines, version, @socket) -> From 87d625b5e047c8eff4fd8a4e30cf05a74d437afb Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 17 Apr 2015 17:32:23 +0100 Subject: [PATCH 3/3] Delay flushes performed after getting an op acknowledgement as well --- .../coffee/ide/editor/sharejs/vendor/client/doc.coffee | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee index c74adad50e..4ac09ad4da 100644 --- a/services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee +++ b/services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee @@ -200,7 +200,7 @@ class Doc callback null, oldInflightOp for callback in @inflightCallbacks # Send the next op. - @flush() + @delayedFlush() else if msg.op # We got a new op from the server. @@ -247,7 +247,7 @@ class Doc # Only one op can be in-flight at a time, so if an op is already on its way then # this method does nothing. flush: => - delete @flushTimeout + @flushTimeout = null #console.log "CALLED FLUSH" return unless @connection.state == 'ok' and @inflightOp == null and @pendingOp != null @@ -279,8 +279,9 @@ class Doc @emit 'change', op - # A timeout is used so if the user sends multiple ops at the same time, they'll be composed - # & sent together. + @delayedFlush() + + delayedFlush: () -> if !@flushTimeout? @flushTimeout = setTimeout @flush, @_flushDelay || 0