diff --git a/coffee/datasource.coffee b/coffee/datasource.coffee index f146ccd..8b7db26 100644 --- a/coffee/datasource.coffee +++ b/coffee/datasource.coffee @@ -55,7 +55,7 @@ class @DataSource viewModelIndex = (row * DATA_POINTS) + col # Create the view model for this grid point - @viewModels[viewModelIndex] ||= {} + @viewModels[viewModelIndex] = {} # Calculate the brightness of a grid point according to how far away it is from the data point @viewModels[viewModelIndex].brightness = Math.min(1, Math.max(0, (DISPLAY_SPREAD - Math.abs(dataPoint - ROW_AMPLITUDES[row])) / DISPLAY_SPREAD)) @@ -91,9 +91,7 @@ class @DataSource doWork: => # Produce the data - Perf.time 'producing data' if PROFILE data = produceData.call(this) - Perf.timeEnd 'producing data' if PROFILE # Notify anyone interested in the data listener(data) for listener in @dataListeners diff --git a/coffee/native.coffee b/coffee/native.coffee index 07974af..8a6d52c 100644 --- a/coffee/native.coffee +++ b/coffee/native.coffee @@ -10,8 +10,6 @@ dataSource = new (getDataSource()) # Attach a handler to that data source, to be called whenever new data is available dataSource.onData (data) -> - Perf.time 'reconstructing DOM' - # Remove the visualizer from the DOM to prevent reflows visualizerElementParent.removeChild(visualizerElement) @@ -31,19 +29,5 @@ dataSource.onData (data) -> # Reattach the visualizer element visualizerElementParent.appendChild visualizerElement - Perf.timeEnd 'reconstructing DOM' - # Make the data source work as fast as possible -workIt = -> - # Work! - dataSource.doWork() - - # Force a synchronous reflow, then schedule another work package. - # NOTE: This is preferable to using requestAnimationFrame, because RAF fires at funny times. - Perf.time 'redrawing' if PROFILE - forceReflow() - Perf.timeEnd 'redrawing' if PROFILE - - # Schedule that next work package - setZeroTimeout workIt -workIt() +setInterval dataSource.doWork.bind(dataSource), 0 diff --git a/coffee/perf.coffee b/coffee/perf.coffee deleted file mode 100644 index e02b870..0000000 --- a/coffee/perf.coffee +++ /dev/null @@ -1,71 +0,0 @@ -# Enable performance profiling? -window.PROFILE = yes - -window.Perf = class - startTimes = {} - results = {} - - # Polyfill for timers - ((w) -> - perfNow = undefined - perfNowNames = ["now", "webkitNow", "msNow", "mozNow"] - unless not w["performance"] - i = 0 - - while i < perfNowNames.length - n = perfNowNames[i] - unless not w["performance"][n] - perfNow = -> - w["performance"][n]() - - break - ++i - perfNow = Date.now unless perfNow - w.perfNow = perfNow - ) window - - # Simple method to average an array of numbers - arrayAverage = do -> - sum = (a, b) -> a + b - (array) -> array.reduce(sum) / array.length - - arrayMin = do -> - compare = (a, b) -> if a < b then a else b - (array) -> array.reduce(compare) - - arrayMax = do -> - compare = (a, b) -> if a > b then a else b - (array) -> array.reduce(compare) - - @time: (key) -> - console.time key - startTimes[key] = perfNow() - @timeEnd: (key) -> - throw 'timeEnd() called without preceding call to time()' unless startTime = startTimes[key] - (results[key] ||= []).push perfNow() - startTime - console.timeEnd key - - @measure: (key, callback) -> - Perf.time key - out = callback() - Perf.timeEnd key - out - - @iqrMean: (key) -> - # What are the results for this key? - resultsForKey = results[key] - return resultsForKey[0] if resultsForKey.length is 1 - # What values are at the boundary of the interquartile range? - lowerBound = ss.quantile(resultsForKey, 0.25) - upperBound = ss.quantile(resultsForKey, 0.75) - # Throw out values outside the interquartile range - middleFifty = [] - for result in resultsForKey - middleFifty.push(result) if lowerBound < result and upperBound > result - # Average the interquartile range - arrayAverage middleFifty - @min: (key) -> arrayMin results[key] - @max: (key) -> arrayMax results[key] - - @results: -> - ("#{key}: max: #{Perf.max(key).toFixed(2)}ms min: #{Perf.min(key).toFixed(2)}ms interquartile mean: #{Perf.iqrMean(key).toFixed(2)}ms" for key of results).join("\n") diff --git a/coffee/react.coffee b/coffee/react.coffee index cc10c9a..23d5b09 100644 --- a/coffee/react.coffee +++ b/coffee/react.coffee @@ -1,25 +1,20 @@ -# Given a data point, produce an object where the keys represent DOM element attributes -dataPointToAttributes = (dataPoint) -> - style: - backgroundColor: "rgba(0,255,0,#{dataPoint.brightness})" +DataPoint = React.createClass + shouldComponentUpdate: (prevProps) -> prevProps.dataPoint.brightness != @props.dataPoint.brightness + render: -> + React.DOM.div (style: (backgroundColor: "rgba(0,255,0,#{@props.dataPoint.brightness})")) # Create a React component for the visualizer VisualizerComponent = React.createClass getInitialState: -> { data: [] } componentDidMount: -> - # Instantiate a data source - @dataSource = @props.dataSource or new BoringDataSource - # Attach a handler to that data source, to be called whenever new data is available - @dataSource.onData (data) => - Perf.time 'setting state' if PROFILE - @replaceState { data: data } - Perf.timeEnd 'setting state' if PROFILE + @props.dataSource.onData (data) => + @setState { data: data } render: -> # Return data point elements, wrapped in a visualizer - React.DOM.div @props, (React.DOM.div dataPointToAttributes(dataPoint) for dataPoint in @state.data) + React.DOM.div @props, ((DataPoint (dataPoint: dataPoint)) for dataPoint in @state.data) # Create a visualizer visualizer = new VisualizerComponent { id: 'visualizer', dataSource: (dataSource = new (getDataSource())) } @@ -32,17 +27,5 @@ app = React.DOM.body null, [visualizer, markerButton] # Render the app React.renderComponent app, document.body, -> - # Make the data source work as fast as possible - workIt = -> - # Work! - dataSource.doWork() - - # Force a synchronous reflow, then schedule another work package. - # NOTE: This is preferable to using requestAnimationFrame, because RAF fires at funny times. - Perf.time 'redrawing' if PROFILE - forceReflow() - Perf.timeEnd 'redrawing' if PROFILE - - # Schedule that next work package - setZeroTimeout workIt - workIt() + setInterval dataSource.doWork.bind(dataSource), 0 + diff --git a/js/fpscounter.js b/js/fpscounter.js new file mode 100644 index 0000000..7dfad77 --- /dev/null +++ b/js/fpscounter.js @@ -0,0 +1 @@ +(function(){var script=document.createElement('script');script.src='http://github.com/mrdoob/stats.js/raw/master/build/stats.min.js';document.body.appendChild(script);script=document.createElement('script');script.innerHTML='var interval=setInterval(function(){if(typeof Stats==\'function\'){clearInterval(interval);var stats=new Stats();stats.domElement.style.position=\'fixed\';stats.domElement.style.left=\'0px\';stats.domElement.style.top=\'0px\';stats.domElement.style.zIndex=\'10000\';document.body.appendChild(stats.domElement);setInterval(function(){stats.update();},1000/60);}},100);';document.body.appendChild(script);})(); \ No newline at end of file diff --git a/native.html b/native.html index 8ad331f..12cdbd9 100644 --- a/native.html +++ b/native.html @@ -20,6 +20,7 @@ + diff --git a/react.html b/react.html index e002392..39904fc 100644 --- a/react.html +++ b/react.html @@ -17,6 +17,7 @@ +