diff --git a/reanimate.cabal b/reanimate.cabal
index 1f0afd7..f09c4c2 100644
--- a/reanimate.cabal
+++ b/reanimate.cabal
@@ -24,9 +24,9 @@ description:
data-files: viewer/build/*.js
viewer/build/*.html
viewer/build/static/js/2.772a56e7.chunk.js
- viewer/build/static/js/main.db22f45d.chunk.js
+ viewer/build/static/js/main.2d848b69.chunk.js
viewer/build/static/js/runtime~main.9eb600ee.js
- viewer/build/static/css/main.6efe09fd.chunk.css
+ viewer/build/static/css/main.f7ad3e9b.chunk.css
data/CIExyz.csv
data/cone_sensitivity_lms.csv
diff --git a/src/Reanimate/Driver.hs b/src/Reanimate/Driver.hs
index d14c53e..01f118b 100644
--- a/src/Reanimate/Driver.hs
+++ b/src/Reanimate/Driver.hs
@@ -1,15 +1,17 @@
module Reanimate.Driver ( reanimate ) where
import Control.Concurrent (MVar, forkIO, killThread, modifyMVar_,
- newEmptyMVar, putMVar)
+ newEmptyMVar, putMVar, takeMVar)
import Control.Exception (finally)
import Control.Monad.Fix (fix)
import qualified Data.Text as T
+import qualified Data.Text.Read as T
import Network.WebSockets
import System.Directory (findFile, listDirectory)
import System.Environment (getArgs, getProgName)
import System.FilePath
import System.FSNotify
+import System.Exit
import System.IO (BufferMode (..), hPutStrLn, hSetBuffering,
stderr, stdin)
@@ -18,7 +20,7 @@ import Paths_reanimate
import Reanimate.Misc (runCmdLazy, runCmd_, withTempDir,
withTempFile)
import Reanimate.Monad (Animation)
-import Reanimate.Render (renderSvgs, render)
+import Reanimate.Render (render, renderSvgs)
import Web.Browser (openBrowser)
opts = defaultConnectionOptions
@@ -56,26 +58,11 @@ reanimate animation = do
sendTextData conn (T.pack "Compiling")
putStrLn "Killing and respawning..."
killThread tid
- tid <- forkIO $ withTempFile ".exe" $ \tmpExecutable -> do
- ret <- runCmd_ "stack" $ ["ghc", "--"] ++ ghcOptions tmpDir ++ [self, "-o", tmpExecutable]
- case ret of
- Left err ->
- sendTextData conn $ T.pack $ "Error" ++ unlines (drop 3 (lines err))
- Right{} -> do
- getFrame <- runCmdLazy tmpExecutable ["once", "+RTS", "-N", "-M200M", "-RTS"]
- flip fix [] $ \loop acc -> do
- frame <- getFrame
- case frame of
- Left "" -> do
- sendTextData conn (T.pack "Done")
- -- insertCache msg (reverse acc)
- Left err -> do
- -- _ <- getChanContents queue
- sendTextData conn $ T.pack $ "Error" ++ err
- Right frame -> do
- sendTextData conn frame
- loop (frame : acc)
+ tid <- forkIO $ slaveHandler conn self tmpDir
return tid
+ killSlave = do
+ tid <- takeMVar slave
+ killThread tid
putStrLn "Found self. Listening..."
stop <- watchFile watch self handler
putMVar slave =<< forkIO (return ())
@@ -83,7 +70,38 @@ reanimate animation = do
fps <- receiveData conn :: IO T.Text
handler
loop
- loop `finally` stop
+ loop `finally` (killSlave >> stop)
+
+slaveHandler conn self tmpDir = withTempFile ".exe" $ \tmpExecutable -> do
+ ret <- runCmd_ "stack" $ ["ghc", "--"] ++ ghcOptions tmpDir ++ [self, "-o", tmpExecutable]
+ case ret of
+ Left err ->
+ sendTextData conn $ T.pack $ "Error" ++ unlines (drop 3 (lines err))
+ Right{} -> do
+ getFrame <- runCmdLazy tmpExecutable ["once", "+RTS", "-N", "-M1G", "-RTS"]
+ (frameCount,_) <- expectFrame =<< getFrame
+ -- sendTextData conn (T.pack "Compiled")
+ sendTextData conn (T.pack $ show frameCount)
+ fix $ \loop -> do
+ (frameIdx, frame) <- expectFrame =<< getFrame
+ sendTextData conn (T.pack $ show frameIdx)
+ sendTextData conn frame
+ loop
+ where
+ expectFrame (Left "") = do
+ sendTextData conn (T.pack "Done")
+ exitWith ExitSuccess
+ expectFrame (Left err) = do
+ sendTextData conn $ T.pack $ "Error" ++ err
+ exitWith (ExitFailure 1)
+ expectFrame (Right frame) =
+ case T.decimal frame of
+ Left err -> do
+ hPutStrLn stderr (T.unpack frame)
+ hPutStrLn stderr $ "expectFrame: " ++ err
+ sendTextData conn $ T.pack $ "Error" ++ err
+ exitWith (ExitFailure 1)
+ Right (frameNumber, rest) -> pure (frameNumber, rest)
watchFile watch file action = watchDir watch (takeDirectory file) check (const action)
where
diff --git a/src/Reanimate/Render.hs b/src/Reanimate/Render.hs
index ab48344..b11be26 100644
--- a/src/Reanimate/Render.hs
+++ b/src/Reanimate/Render.hs
@@ -23,16 +23,38 @@ import Text.Printf (printf)
renderSvgs :: Animation -> IO ()
renderSvgs ani = do
- let renderedFrames = map (T.concat . T.lines . T.pack . nthFrame) frames
- mapM_ T.putStrLn (renderedFrames `using` parBuffer 16 rdeepseq)
+ print frameCount
+ lock <- newMVar ()
+ -- let renderedFrames = map (T.concat . T.lines . T.pack . nthFrame) frames
+ -- mapM_ T.putStrLn (renderedFrames `using` parBuffer 16 rdeepseq)
+
+ concurrentForM_ (frameOrder rate frameCount) $ \nth -> do
+ let -- frame = frameAt (recip (fromIntegral rate-1) * fromIntegral nth) ani
+ now = (duration ani / (fromIntegral frameCount-1)) * fromIntegral nth
+ frame = frameAt (if frameCount<=1 then 0 else now) ani
+ svg = renderSvg Nothing Nothing frame
+ evaluate (length svg)
+ withMVar lock $ \_ -> do
+ putStr (show nth)
+ T.putStrLn $ T.concat . T.lines . T.pack $ svg
+ hFlush stdout
where
frames = [0..frameCount-1]
rate = 60
nthFrame nth = renderSvg Nothing Nothing $ frameAt (recip (fromIntegral rate) * fromIntegral nth) ani
frameCount = round (duration ani * fromIntegral rate) :: Int
- nameTemplate :: String
- nameTemplate = "render-%05d.svg"
+frameOrder :: Int -> Int -> [Int]
+frameOrder fps nFrames = worker [] fps
+ where
+ worker seen 0 = []
+ worker seen nthFrame =
+ filterFrameList seen nthFrame nFrames ++
+ worker (nthFrame : seen) (nthFrame `div` 2)
+filterFrameList seen nthFrame nFrames =
+ filter (not.isSeen) $ [0, nthFrame .. nFrames-1]
+ where
+ isSeen x = any (\y -> x `mod` y == 0) seen
data Format = RenderMp4 | RenderGif | RenderWebm | RenderBlank
diff --git a/viewer/build/asset-manifest.json b/viewer/build/asset-manifest.json
index 720fb8b..011e920 100644
--- a/viewer/build/asset-manifest.json
+++ b/viewer/build/asset-manifest.json
@@ -1,13 +1,13 @@
{
- "main.css": "./static/css/main.6efe09fd.chunk.css",
- "main.js": "./static/js/main.db22f45d.chunk.js",
- "main.js.map": "./static/js/main.db22f45d.chunk.js.map",
+ "main.css": "./static/css/main.f7ad3e9b.chunk.css",
+ "main.js": "./static/js/main.2d848b69.chunk.js",
+ "main.js.map": "./static/js/main.2d848b69.chunk.js.map",
"runtime~main.js": "./static/js/runtime~main.9eb600ee.js",
"runtime~main.js.map": "./static/js/runtime~main.9eb600ee.js.map",
"static/js/2.772a56e7.chunk.js": "./static/js/2.772a56e7.chunk.js",
"static/js/2.772a56e7.chunk.js.map": "./static/js/2.772a56e7.chunk.js.map",
"index.html": "./index.html",
- "precache-manifest.15b8d497ab10704d87b84878b92d08cf.js": "./precache-manifest.15b8d497ab10704d87b84878b92d08cf.js",
+ "precache-manifest.ecac8766a6ef020d2fc520bbf4682e03.js": "./precache-manifest.ecac8766a6ef020d2fc520bbf4682e03.js",
"service-worker.js": "./service-worker.js",
- "static/css/main.6efe09fd.chunk.css.map": "./static/css/main.6efe09fd.chunk.css.map"
+ "static/css/main.f7ad3e9b.chunk.css.map": "./static/css/main.f7ad3e9b.chunk.css.map"
}
\ No newline at end of file
diff --git a/viewer/build/index.html b/viewer/build/index.html
index 602bc31..788d452 100644
--- a/viewer/build/index.html
+++ b/viewer/build/index.html
@@ -1 +1 @@
-
\n
\n );\n }\n}\n\nexport default App;\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read http://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.1/8 is considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit http://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl)\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport * as serviceWorker from './serviceWorker';\n\nReactDOM.render(
, document.getElementById('root'));\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: http://bit.ly/CRA-PWA\nserviceWorker.unregister();\n"],"sourceRoot":""}
\ No newline at end of file
diff --git a/viewer/build/static/js/main.db22f45d.chunk.js b/viewer/build/static/js/main.db22f45d.chunk.js
deleted file mode 100644
index 917516f..0000000
--- a/viewer/build/static/js/main.db22f45d.chunk.js
+++ /dev/null
@@ -1,2 +0,0 @@
-(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{10:function(e,n,t){e.exports=t(18)},16:function(e,n,t){},17:function(e,n,t){},18:function(e,n,t){"use strict";t.r(n);var s=t(0),a=t.n(s),o=t(4),r=t.n(o),i=(t(16),t(2)),c=t(5),l=t(6),m=t(9),g=t(7),u=t(1),d=t(8),v=(t(17),function(e){function n(e){var t;Object(c.a)(this,n),(t=Object(m.a)(this,Object(g.a)(n).call(this,e))).connect=function(){var e=new WebSocket("ws://localhost:9161");e.onopen=function(n){t.setState(function(e){return Object(i.a)({},e,{message:"Connected."})}),e.send("60")},e.onclose=function(e){t.setState(function(e){return Object(i.a)({},e,{message:"Disconnected."})}),setTimeout(t.connect,1e3)},e.onmessage=function(e){if("Success!"===e.data)console.log("Success");else if("Compiling"===e.data)t.setState({message:"Compiling..."});else if("Rendering"===e.data)t.setState({message:"Rendering..."}),t.nFrames_new=0,t.svgs_new=[];else if("Done"===e.data)t.setState({message:""}),console.log("Done"),t.nFrames=t.nFrames_new,t.svgs=t.svgs_new,t.nFrames_new=0,t.svgs_new=[],t.start=Date.now();else if(e.data.startsWith("Error"))console.log("Error"),t.setState({message:e.data.substring(5)});else{t.setState({message:"Rendering: ".concat(t.nFrames_new)}),t.nFrames_new++;var n=document.createElement("div");n.innerHTML=e.data,t.svgs_new.push(n)}},t.setState(function(n){return Object(i.a)({},n,{socket:e,message:"Connecting..."})})},t.onLoad=function(e){setTimeout(function(){e.resize()},0)},t.state={},setTimeout(t.connect,0),t.nFrames_new=0,t.svgs_new=[],t.nFrames=0,t.svgs=[],t.start=Date.now();var s=Object(u.a)(t);return requestAnimationFrame(function e(){var n=Date.now(),a=s.nFrames,o=Math.round((n-t.start)/1e3*60)%a;if(s.svgs_new.length){for(;s.svg.firstChild;)s.svg.removeChild(s.svg.firstChild);s.svg.appendChild(s.svgs_new[s.svgs_new.length-1])}else if(a){for(;s.svg.firstChild;)s.svg.removeChild(s.svg.firstChild);s.svg.appendChild(s.svgs[o])}else s.svg.innerText="";requestAnimationFrame(e)}),t}return Object(d.a)(n,e),Object(l.a)(n,[{key:"render",value:function(){var e=this,n=this.state.message;return a.a.createElement("div",{className:"App"},a.a.createElement("div",{className:"viewer"},a.a.createElement("div",{ref:function(n){return e.svg=n}}),a.a.createElement("div",{className:"messages"},a.a.createElement("pre",null,n))))}}]),n}(s.Component));Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));r.a.render(a.a.createElement(v,null),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then(function(e){e.unregister()})}},[[10,1,2]]]);
-//# sourceMappingURL=main.db22f45d.chunk.js.map
\ No newline at end of file
diff --git a/viewer/build/static/js/main.db22f45d.chunk.js.map b/viewer/build/static/js/main.db22f45d.chunk.js.map
deleted file mode 100644
index 2a6b617..0000000
--- a/viewer/build/static/js/main.db22f45d.chunk.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["App.jsx","serviceWorker.js","index.js"],"names":["App","props","_this","Object","classCallCheck","this","possibleConstructorReturn","getPrototypeOf","call","connect","ws","WebSocket","onopen","event","setState","state","objectSpread","message","send","onclose","setTimeout","onmessage","data","console","log","nFrames_new","svgs_new","nFrames","svgs","start","Date","now","startsWith","substring","concat","div","document","createElement","innerHTML","push","socket","onLoad","ace","resize","self","assertThisInitialized","requestAnimationFrame","animate","thisFrame","Math","round","length","svg","firstChild","removeChild","appendChild","innerText","_this2","react_default","a","className","ref","node","Component","Boolean","window","location","hostname","match","ReactDOM","render","src_App_0","getElementById","navigator","serviceWorker","ready","then","registration","unregister"],"mappings":"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","file":"static/js/main.db22f45d.chunk.js","sourcesContent":["import React, {Component} from 'react';\nimport './App.css';\n\nclass App extends Component {\n connect = () => {\n // const ws = new WebSocket(\"wss://reanimate.clozecards.com:9160\");\n const ws = new WebSocket(\"ws://localhost:9161\");\n\n ws.onopen = event => {\n this.setState(state => ({\n ...state,\n message: \"Connected.\"\n }));\n ws.send('60');\n }\n ws.onclose = event => {\n this.setState(state => ({\n ...state,\n message: \"Disconnected.\"\n }));\n setTimeout(this.connect, 1000);\n }\n ws.onmessage = event => {\n if (event.data === \"Success!\") {\n console.log(\"Success\");\n } else if (event.data === \"Compiling\") {\n this.setState({message: \"Compiling...\"});\n } else if (event.data === \"Rendering\") {\n this.setState({message: \"Rendering...\"});\n this.nFrames_new = 0;\n this.svgs_new = [];\n } else if (event.data === \"Done\") {\n this.setState({message: \"\"});\n console.log(\"Done\");\n this.nFrames = this.nFrames_new;\n this.svgs = this.svgs_new;\n this.nFrames_new = 0;\n this.svgs_new = [];\n this.start = Date.now();\n } else if (event.data.startsWith(\"Error\")) {\n console.log(\"Error\");\n this.setState({message: event.data.substring(5)});\n } else {\n this.setState({message: `Rendering: ${this.nFrames_new}`});\n this.nFrames_new++;\n const div = document.createElement('div');\n div.innerHTML = event.data;\n this.svgs_new.push(div);\n }\n }\n this.setState(state => ({\n ...state,\n socket: ws,\n message: \"Connecting...\"\n }));\n }\n constructor(props) {\n super(props);\n\n this.state = {\n };\n setTimeout(this.connect, 0);\n this.nFrames_new = 0;\n this.svgs_new = [];\n this.nFrames = 0;\n this.svgs = [];\n this.start = Date.now();\n const self = this;\n const animate = () => {\n const now = Date.now();\n const nFrames = self.nFrames;\n const thisFrame = (Math.round((now - this.start) / 1000 * 60)) % nFrames\n // const thisFrame = 0; console.log('Animation frame:', thisFrame, nFrames);\n if (self.svgs_new.length) {\n while (self.svg.firstChild)\n self.svg.removeChild(self.svg.firstChild);\n self.svg.appendChild(self.svgs_new[self.svgs_new.length-1]);\n } else {\n if (nFrames) {\n // self.svg.innerHTML = self.svgs[thisFrame];\n while (self.svg.firstChild)\n self.svg.removeChild(self.svg.firstChild);\n self.svg.appendChild(self.svgs[thisFrame]);\n } else {\n self.svg.innerText = \"\";\n }\n }\n requestAnimationFrame(animate);\n };\n requestAnimationFrame(animate);\n }\n onLoad = ace => {\n setTimeout(function() {\n ace.resize();\n }, 0);\n }\n render() {\n const {message} = this.state;\n return (\n
\n
\n );\n }\n}\n\nexport default App;\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read http://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.1/8 is considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit http://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl)\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport * as serviceWorker from './serviceWorker';\n\nReactDOM.render(
, document.getElementById('root'));\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: http://bit.ly/CRA-PWA\nserviceWorker.unregister();\n"],"sourceRoot":""}
\ No newline at end of file
diff --git a/viewer/src/App.css b/viewer/src/App.css
index 79dde98..32577c6 100755
--- a/viewer/src/App.css
+++ b/viewer/src/App.css
@@ -47,7 +47,6 @@ div.messages pre {
margin-right: 2em;
}
-
#editor {
}
diff --git a/viewer/src/App.jsx b/viewer/src/App.jsx
index c2fe0a7..5cd1a42 100755
--- a/viewer/src/App.jsx
+++ b/viewer/src/App.jsx
@@ -25,27 +25,46 @@ class App extends Component {
console.log("Success");
} else if (event.data === "Compiling") {
this.setState({message: "Compiling..."});
- } else if (event.data === "Rendering") {
- this.setState({message: "Rendering..."});
- this.nFrames_new = 0;
- this.svgs_new = [];
+ this.status = 'compiling';
+ this.svgs = [];
+ this.frame_count = 0;
+ this.next_frame = 0;
} else if (event.data === "Done") {
this.setState({message: ""});
console.log("Done");
- this.nFrames = this.nFrames_new;
- this.svgs = this.svgs_new;
- this.nFrames_new = 0;
- this.svgs_new = [];
this.start = Date.now();
} else if (event.data.startsWith("Error")) {
- console.log("Error");
+ console.log("Error", event.data.substring(5));
this.setState({message: event.data.substring(5)});
} else {
- this.setState({message: `Rendering: ${this.nFrames_new}`});
- this.nFrames_new++;
- const div = document.createElement('div');
- div.innerHTML = event.data;
- this.svgs_new.push(div);
+ const num = parseInt(event.data);
+ if(isNaN(num)) {
+ // this.setState({message: `Rendering: ${this.nFrames_new}`});
+ // this.nFrames_new++;
+ // const div = document.createElement('div');
+ // div.innerHTML = event.data;
+ // this.svgs_new.push(div);
+ const div = document.createElement('div');
+ div.innerHTML = event.data;
+ this.svgs[this.next_frame] = div;
+ var count = 0;
+ this.svgs.forEach(_ => count++);
+ console.log('Received', this.next_frame, this.frame_count, count);
+ } else {
+
+ if( this.status === 'compiling' ) {
+ this.setState({message: `Rendering...`});
+ this.frame_count = num;
+ this.status = 'rendering';
+ this.start = Date.now();
+ this.svgs = [];
+ this.svgs[this.frame_count-1] = undefined;
+ } else if( this.status === 'rendering' ) {
+ this.next_frame = num;
+ } else {
+ console.log("Bad state change: received number");
+ }
+ }
}
}
this.setState(state => ({
@@ -60,30 +79,33 @@ class App extends Component {
this.state = {
};
setTimeout(this.connect, 0);
- this.nFrames_new = 0;
- this.svgs_new = [];
- this.nFrames = 0;
this.svgs = [];
this.start = Date.now();
+
+ this.status = '';
+ this.frame_count = 0;
+ this.next_frame = 0;
+
const self = this;
const animate = () => {
const now = Date.now();
- const nFrames = self.nFrames;
+ const nFrames = self.frame_count;
+ const aniDuration = self.frame_count/60;
const thisFrame = (Math.round((now - this.start) / 1000 * 60)) % nFrames
// const thisFrame = 0; console.log('Animation frame:', thisFrame, nFrames);
- if (self.svgs_new.length) {
- while (self.svg.firstChild)
- self.svg.removeChild(self.svg.firstChild);
- self.svg.appendChild(self.svgs_new[self.svgs_new.length-1]);
- } else {
- if (nFrames) {
- // self.svg.innerHTML = self.svgs[thisFrame];
+ var count = 0;
+ this.svgs.forEach(_ => count++);
+ if (nFrames) {
+ // self.svg.innerHTML = self.svgs[thisFrame];
+ if(self.svgs[thisFrame]) {
+ // self.hud.innerText = '' + thisFrame + '/' + self.frame_count;
+ this.setState({message: '' + thisFrame + '/' + self.frame_count + ' ' + Math.round(count/aniDuration) + ' fps'});
while (self.svg.firstChild)
self.svg.removeChild(self.svg.firstChild);
self.svg.appendChild(self.svgs[thisFrame]);
- } else {
- self.svg.innerText = "";
}
+ } else {
+ self.svg.innerText = "";
}
requestAnimationFrame(animate);
};