From de4d9951908a8ab95af0ee7bf403ca7feb55eb90 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 19 Feb 2015 14:26:39 -0700 Subject: [PATCH] Add document coordination methods to ViewRegistry These will assist in updating and reading the DOM in a non-blocking manner across components. --- spec/view-registry-spec.coffee | 74 ++++++++++++++++++++++++++++++++++ src/view-registry.coffee | 45 +++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/spec/view-registry-spec.coffee b/spec/view-registry-spec.coffee index 851faf109..90691578f 100644 --- a/spec/view-registry-spec.coffee +++ b/spec/view-registry-spec.coffee @@ -85,3 +85,77 @@ describe "ViewRegistry", -> expect(registry.getView(new TestModel) instanceof TestView).toBe true disposable.dispose() expect(-> registry.getView(new TestModel)).toThrow() + + describe "::updateDocument(fn) and ::readDocument(fn)", -> + frameRequests = null + + beforeEach -> + frameRequests = [] + spyOn(window, 'requestAnimationFrame').andCallFake (fn) -> frameRequests.push(fn) + + it "performs all pending writes before all pending reads on the next animation frame", -> + events = [] + + registry.updateDocument -> events.push('write 1') + registry.readDocument -> events.push('read 1') + registry.readDocument -> events.push('read 2') + registry.updateDocument -> events.push('write 2') + + expect(events).toEqual [] + + expect(frameRequests.length).toBe 1 + frameRequests[0]() + expect(events).toEqual ['write 1', 'write 2', 'read 1', 'read 2'] + + frameRequests = [] + events = [] + disposable = registry.updateDocument -> events.push('write 3') + registry.updateDocument -> events.push('write 4') + registry.readDocument -> events.push('read 3') + + disposable.dispose() + + expect(frameRequests.length).toBe 1 + frameRequests[0]() + expect(events).toEqual ['write 4', 'read 3'] + + it "pauses DOM polling when reads or writes are pending", -> + spyOn(window, 'setInterval').andCallFake(fakeSetInterval) + spyOn(window, 'clearInterval').andCallFake(fakeClearInterval) + events = [] + + registry.pollDocument -> events.push('poll') + registry.updateDocument -> events.push('write') + registry.readDocument -> events.push('read') + + advanceClock(registry.documentPollingInterval) + + frameRequests[0]() + expect(events).toEqual ['write', 'read'] + + advanceClock(registry.documentPollingInterval) + expect(events).toEqual ['write', 'read', 'poll'] + + describe "::pollDocument(fn)", -> + it "calls all registered reader functions on an interval until they are disabled via a returned disposable", -> + spyOn(window, 'setInterval').andCallFake(fakeSetInterval) + + events = [] + disposable1 = registry.pollDocument -> events.push('poll 1') + disposable2 = registry.pollDocument -> events.push('poll 2') + + expect(events).toEqual [] + + advanceClock(registry.documentPollingInterval) + expect(events).toEqual ['poll 1', 'poll 2'] + + advanceClock(registry.documentPollingInterval) + expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2'] + + disposable1.dispose() + advanceClock(registry.documentPollingInterval) + expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2', 'poll 2'] + + disposable2.dispose() + advanceClock(registry.documentPollingInterval) + expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2', 'poll 2'] diff --git a/src/view-registry.coffee b/src/view-registry.coffee index a9104af5f..f3750171d 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -42,9 +42,14 @@ Grim = require 'grim' # ``` module.exports = class ViewRegistry + documentPollingInterval: 200 + constructor: -> @views = new WeakMap @providers = [] + @documentWriters = [] + @documentReaders = [] + @documentPollers = [] # Essential: Add a provider that will be used to construct views in the # workspace's view layer based on model objects in its model layer. @@ -150,3 +155,43 @@ class ViewRegistry findProvider: (object) -> find @providers, ({modelConstructor}) -> object instanceof modelConstructor + + updateDocument: (fn) -> + @documentWriters.push(fn) + @requestDocumentUpdate() + new Disposable => + @documentWriters = @documentWriters.filter (writer) -> writer isnt fn + + readDocument: (fn) -> + @documentReaders.push(fn) + @requestDocumentUpdate() + new Disposable => + @documentReaders = @documentReaders.filter (reader) -> reader isnt fn + + pollDocument: (fn) -> + @startPollingDocument() if @documentPollers.length is 0 + @documentPollers.push(fn) + new Disposable => + @documentPollers = @documentPollers.filter (poller) -> poller isnt fn + @stopPollingDocument() if @documentPollers.length is 0 + + requestDocumentUpdate: -> + unless @documentUpdateRequested + @documentUpdateRequested = true + @stopPollingDocument() + requestAnimationFrame(@performDocumentUpdate) + + performDocumentUpdate: => + @documentUpdateRequested = false + @startPollingDocument() + writer() while writer = @documentWriters.shift() + reader() while reader = @documentReaders.shift() + + startPollingDocument: -> + @pollIntervalHandle = window.setInterval(@performDocumentPoll, @documentPollingInterval) + + stopPollingDocument: -> + window.clearInterval(@pollIntervalHandle) + + performDocumentPoll: => + poller() for poller in @documentPollers