Skip to content
Browse files

* Make webworker-threads an optional dependency.

  If WebWorker Threads is unavailable, use vm.createContext
  as a same-thread fallback as before.

  Initial micro-benchmark shows that threads offer 2x throughput
  even when concurrency=1.
  • Loading branch information...
1 parent 7a82251 commit 0f41d2032483388533ce9b62faa7902944efbb05 @audreyt committed Dec 13, 2012
Showing with 513 additions and 272 deletions.
  1. +30 −6 main.js
  2. +3 −2 package.json
  3. +3 −1 package.ls
  4. +325 −174 sc.js
  5. +15 −7 src/main.ls
  6. +137 −82 src/sc.ls
View
36 main.js
@@ -86,8 +86,15 @@
snapshot = arg$.snapshot;
if (snapshot) {
ref$ = cb.call(this$.params, snapshot), type = ref$[0], content = ref$[1];
- this$.response.type(type);
- return this$.response.send(200, content);
+ if (content instanceof Function) {
+ return content(SC[this$.params.room], function(rv){
+ this$.response.type(type);
+ return this$.response.send(200, rv);
+ });
+ } else {
+ this$.response.type(type);
+ return this$.response.send(200, content);
+ }
} else {
this$.response.type(Text);
return this$.response.send(404, '');
@@ -107,14 +114,24 @@
});
this.get({
'/_/:room/html': api(function(){
- var ref$;
- return [Html, (ref$ = SC[this.room]) != null ? ref$.CreateSheetHTML() : void 8];
+ return [
+ Html, function(sc, cb){
+ return sc.exportHTML(function(html){
+ return cb(html);
+ });
+ }
+ ];
})
});
this.get({
'/_/:room/csv': api(function(){
- var ref$;
- return [Csv, (ref$ = SC[this.room]) != null ? ref$.SocialCalc.ConvertSaveToOtherFormat((ref$ = SC[this.room]) != null ? ref$.CreateSheetSave() : void 8, 'csv') : void 8];
+ return [
+ Csv, function(sc, cb){
+ return sc.exportCSV(function(csv){
+ return cb(csv);
+ });
+ }
+ ];
})
});
this.get({
@@ -189,6 +206,9 @@
continue CleanRoom;
}
}
+ if ((ref$ = SC[room]) != null) {
+ ref$.terminate();
+ }
delete SC[room];
}
}
@@ -275,6 +295,10 @@
DB.del(['audit', 'log', 'chat', 'ecell', 'snapshot'].map(function(it){
return it + "-" + room;
}), function(){
+ var ref$;
+ if ((ref$ = SC[room]) != null) {
+ ref$.terminate();
+ }
delete SC[room];
return broadcast(this$.data);
});
View
5 package.json
@@ -1,7 +1,7 @@
{
"name": "ethercalc",
"description": "Multi-User Spreadsheet Server",
- "version": "0.20121111.0",
+ "version": "0.20121213.0",
"homepage": "http://ethercalc.net/",
"repository": {
"type": "git",
@@ -15,7 +15,8 @@
},
"optionalDependencies": {
"hiredis": "0.1.x",
- "LiveScript": "1.1.x"
+ "LiveScript": "1.1.x",
+ "webworkerThreads": "0.4.x"
},
"directories": {
"bin": "./bin"
View
4 package.ls 100644 → 100755
@@ -1,6 +1,7 @@
+#!/usr/bin/env lsc -cj
name: \ethercalc
description: 'Multi-User Spreadsheet Server'
-version: \0.20121111.0
+version: \0.20121213.0
homepage: 'http://ethercalc.net/'
repository:
type: 'git'
@@ -13,6 +14,7 @@ dependencies:
optional-dependencies:
hiredis: \0.1.x
LiveScript: \1.1.x
+ webworker-threads: \0.4.x
directories:
bin: \./bin
subdomain: \ethercalc
View
499 sc.js
@@ -1,10 +1,65 @@
(function(){
- var vm, fs, path, bootSC, Node, replace$ = ''.replace, join$ = [].join;
+ var vm, fs, path, bootSC, Worker, e;
vm = require('vm');
fs = require('fs');
path = require('path');
bootSC = fs.readFileSync(path.dirname(fs.realpathSync(__filename)) + "/SocialCalcModule.js", 'utf8');
global.SC == null && (global.SC = {});
+ Worker = (function(){
+ try {
+ return require('xebworker-threads').Worker;
+ } catch (e$) {
+ e = e$;
+ return (function(){
+ var prototype = constructor.prototype;
+ function constructor(code){
+ var vm, cxt, sandbox, this$ = this;
+ vm = require('vm');
+ cxt = {
+ console: console,
+ self: {
+ onmessage: function(){}
+ }
+ };
+ cxt.window = {
+ setTimeout: function(cb, ms){
+ return process.nextTick(cb);
+ },
+ clearTimeout: function(){}
+ };
+ this.postMessage = function(data){
+ return sandbox.self.onmessage({
+ data: data
+ });
+ };
+ this.thread = cxt.thread = {
+ nextTick: function(cb){
+ return process.nextTick(cb);
+ },
+ eval: function(src, cb){
+ var rv, e;
+ try {
+ rv = vm.runInContext(src, sandbox);
+ return typeof cb === 'function' ? cb(null, rv) : void 8;
+ } catch (e$) {
+ e = e$;
+ return typeof cb === 'function' ? cb(e) : void 8;
+ }
+ }
+ };
+ this.terminate = function(){};
+ this.sandbox = sandbox = vm.createContext(cxt);
+ sandbox.postMessage = function(data){
+ return typeof this$.onmessage === 'function' ? this$.onmessage({
+ data: data
+ }) : void 8;
+ };
+ vm.runInContext("(" + code + ")()", sandbox);
+ }
+ return constructor;
+ }());
+ }
+ }());
this.include = function(){
var DB;
DB = this.include('db');
@@ -37,192 +92,288 @@
});
};
SC._init = function(snapshot, log, DB, room, io){
- var sandbox, SocialCalc, ss, parts, cmdstr, line;
+ var w;
log == null && (log = []);
if (SC[room] != null) {
SC[room]._doClearCache();
return SC[room];
}
- sandbox = vm.createContext({
- SocialCalc: null,
- ss: null,
- window: {
- setTimeout: function(cb, ms){
- return process.nextTick(cb);
- },
- clearTimeout: function(){}
- },
- console: console
+ w = new Worker(function(){
+ return self.onmessage = function(arg$){
+ var ref$, type, ref, snapshot, command, room, log, ref1$, csv, ss, parts, cmdstr, line, Node;
+ ref$ = arg$.data, type = ref$.type, ref = ref$.ref, snapshot = ref$.snapshot, command = ref$.command, room = ref$.room, log = (ref1$ = ref$.log) != null
+ ? ref1$
+ : [];
+ switch (type) {
+ case 'cmd':
+ return window.ss.ExecuteCommand(command);
+ case 'recalc':
+ return SocialCalc.RecalcLoadedSheet(ref, snapshot, true);
+ case 'clearCache':
+ return SocialCalc.Formula.SheetCache.sheets = {};
+ case 'exportSave':
+ return postMessage({
+ type: 'save',
+ save: window.ss.CreateSheetSave()
+ });
+ case 'exportHTML':
+ return postMessage({
+ type: 'html',
+ html: window.ss.CreateSheetHTML()
+ });
+ case 'exportCSV':
+ csv = window.ss.SocialCalc.ConvertSaveToOtherFormat(window.ss.CreateSheetSave(), 'csv');
+ return postMessage({
+ type: 'csv',
+ csv: csv
+ });
+ case 'init':
+ SocialCalc.SaveEditorSettings = function(){
+ return "";
+ };
+ SocialCalc.CreateAuditString = function(){
+ return "";
+ };
+ SocialCalc.CalculateEditorPositions = function(){};
+ SocialCalc.Popup.Types.List.Create = function(){};
+ SocialCalc.Popup.Types.ColorChooser.Create = function(){};
+ SocialCalc.Popup.Initialize = function(){};
+ SocialCalc.RecalcInfo.LoadSheet = function(ref){
+ ref = (ref + "").replace(/[^a-zA-Z0-9]+/g, '').toLowerCase();
+ postMessage({
+ type: 'load-sheet',
+ ref: ref
+ });
+ return true;
+ };
+ window.setTimeout = function(cb, ms){
+ return thread.nextTick(cb);
+ };
+ window.clearTimeout = function(){};
+ window.ss = ss = new SocialCalc.SpreadsheetControl;
+ ss.SocialCalc = SocialCalc;
+ ss._room = room;
+ if (snapshot) {
+ parts = ss.DecodeSpreadsheetSave(snapshot);
+ }
+ ss.editor.StatusCallback.EtherCalc = {
+ func: function(editor, status, arg){
+ var newSnapshot;
+ if (!(status === 'doneposcalc' && !ss.editor.busy)) {
+ return;
+ }
+ newSnapshot = ss.CreateSpreadsheetSave();
+ if (ss._snapshot === newSnapshot) {
+ return;
+ }
+ ss._snapshot = newSnapshot;
+ return postMessage({
+ type: 'snapshot',
+ snapshot: newSnapshot
+ });
+ }
+ };
+ if (parts != null && parts.sheet) {
+ ss.sheet.ResetSheet();
+ ss.ParseSheetSave(snapshot.substring(parts.sheet.start, parts.sheet.end));
+ }
+ cmdstr = (function(){
+ var i$, ref$, len$, results$ = [];
+ for (i$ = 0, len$ = (ref$ = log).length; i$ < len$; ++i$) {
+ line = ref$[i$];
+ if (!/^re(calc|display)$/.test(line)) {
+ results$.push(line);
+ }
+ }
+ return results$;
+ }()).join("\n");
+ if (cmdstr.length) {
+ cmdstr += "\n";
+ }
+ ss.context.sheetobj.ScheduleSheetCommands("set sheet defaulttextvalueformat text-wiki\n" + cmdstr + "recalc\n", false, true);
+ Node = (function(){
+ Node.displayName = 'Node';
+ var prototype = Node.prototype, constructor = Node;
+ function Node(tag, attrs, style, elems, raw){
+ this.tag = tag != null ? tag : "div";
+ this.attrs = attrs != null
+ ? attrs
+ : {};
+ this.style = style != null
+ ? style
+ : {};
+ this.elems = elems != null
+ ? elems
+ : [];
+ this.raw = raw != null ? raw : '';
+ }
+ Object.defineProperty(prototype, 'id', {
+ set: function(id){
+ this.attrs.id = id;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ Object.defineProperty(prototype, 'width', {
+ set: function(width){
+ this.attrs.width = width;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ Object.defineProperty(prototype, 'height', {
+ set: function(height){
+ this.attrs.height = height;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ Object.defineProperty(prototype, 'className', {
+ set: function($class){
+ this.attrs['class'] = $class;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ Object.defineProperty(prototype, 'innerHTML', {
+ set: function(raw){
+ this.raw = raw;
+ },
+ get: function(){
+ var e;
+ return this.raw || (function(){
+ var i$, ref$, len$, results$ = [];
+ for (i$ = 0, len$ = (ref$ = this.elems).length; i$ < len$; ++i$) {
+ e = ref$[i$];
+ results$.push(e.outerHTML);
+ }
+ return results$;
+ }.call(this)).join("\n");
+ },
+ configurable: true,
+ enumerable: true
+ });
+ Object.defineProperty(prototype, 'outerHTML', {
+ get: function(){
+ var tag, attrs, style, css, k, v;
+ tag = this.tag, attrs = this.attrs, style = this.style;
+ css = style.cssText || (function(){
+ var ref$, results$ = [];
+ for (k in ref$ = style) {
+ v = ref$[k];
+ results$.push(k + ":" + v);
+ }
+ return results$;
+ }()).join(";");
+ if (css) {
+ attrs.style = css;
+ } else {
+ delete attrs.style;
+ }
+ return "<" + tag + (function(){
+ var ref$, results$ = [];
+ for (k in ref$ = attrs) {
+ v = ref$[k];
+ results$.push(" " + k + "=\"" + v + "\"");
+ }
+ return results$;
+ }()).join('') + ">" + this.innerHTML + "</" + tag + ">";
+ },
+ configurable: true,
+ enumerable: true
+ });
+ prototype.appendChild = function(it){
+ return this.elems.push(it);
+ };
+ return Node;
+ }());
+ return SocialCalc.document.createElement = function(it){
+ return new Node(it);
+ };
+ }
+ };
});
- vm.runInContext(bootSC, sandbox);
- SocialCalc = sandbox.SocialCalc;
- SocialCalc.SaveEditorSettings = function(){
- return "";
- };
- SocialCalc.CreateAuditString = function(){
- return "";
+ w._snapshot = snapshot;
+ w.thread.eval(bootSC);
+ w.postMessage({
+ type: 'init',
+ room: room,
+ log: log,
+ snapshot: snapshot
+ });
+ w.onSnapshot = function(newSnapshot){
+ var this$ = this;
+ io.sockets['in']("recalc." + room).emit('data', {
+ type: 'recalc',
+ snapshot: newSnapshot,
+ force: true,
+ room: room
+ });
+ w._snapshot = newSnapshot;
+ return DB.multi().set("snapshot-" + room, newSnapshot).del("log-" + room).bgsave().exec(function(){
+ return console.log("==> Regenerated snapshot for " + room);
+ });
};
- SocialCalc.CalculateEditorPositions = function(){};
- SocialCalc.Popup.Types.List.Create = function(){};
- SocialCalc.Popup.Types.ColorChooser.Create = function(){};
- SocialCalc.Popup.Initialize = function(){};
- vm.runInContext('ss = new SocialCalc.SpreadsheetControl', sandbox);
- SocialCalc.RecalcInfo.LoadSheet = function(ref){
- var serialization, parts;
- ref = (replace$.call(ref, /[^a-zA-Z0-9]+/g, '')).toLowerCase();
- if (SC[ref]) {
- serialization = SC[ref].CreateSpreadsheetSave();
- parts = SC[ref].DecodeSpreadsheetSave(serialization);
- SocialCalc.RecalcLoadedSheet(ref, serialization.substring(parts.sheet.start, parts.sheet.end), true);
- } else {
- SocialCalc.RecalcLoadedSheet(ref, '', true);
+ w.onmessage = function(arg$){
+ var ref$, type, snapshot, html, csv, ref, parts, save;
+ ref$ = arg$.data, type = ref$.type, snapshot = ref$.snapshot, html = ref$.html, csv = ref$.csv, ref = ref$.ref, parts = ref$.parts, save = ref$.save;
+ switch (type) {
+ case 'snapshot':
+ return w.onSnapshot(snapshot);
+ case 'save':
+ return w.onSave(save);
+ case 'html':
+ return w.onHtml(html);
+ case 'csv':
+ return w.onCsv(csv);
+ case 'load-sheet':
+ return SC._get(ref, io, function(){
+ if (SC[ref]) {
+ return SC[ref].exportSave(function(save){
+ return w.postMessage({
+ type: 'recalc',
+ ref: ref,
+ snapshot: save
+ });
+ });
+ } else {
+ return w.postMessage({
+ type: 'recalc',
+ ref: ref,
+ snapshot: ''
+ });
+ }
+ });
}
- return true;
};
- ss = sandbox.ss;
- ss.SocialCalc = SocialCalc;
- ss._room = room;
- ss._doClearCache = function(){
- return SocialCalc.Formula.SheetCache.sheets = {};
+ w._doClearCache = function(){
+ return this.postMessage({
+ type: 'clearCache'
+ });
};
- ss.editor.StatusCallback.EtherCalc = {
- func: function(editor, status, arg){
- var newSnapshot, this$ = this;
- if (!(status === 'doneposcalc' && !ss.editor.busy)) {
- return;
- }
- newSnapshot = ss.CreateSpreadsheetSave();
- if (ss._snapshot === newSnapshot) {
- return;
- }
- io.sockets['in']("recalc." + room).emit('data', {
- type: 'recalc',
- snapshot: newSnapshot,
- force: true,
- room: room
- });
- ss._snapshot = newSnapshot;
- return DB.multi().set("snapshot-" + room, newSnapshot).del("log-" + room).bgsave().exec(function(){
- return console.log("==> Regenerated snapshot for " + room);
- });
- }
+ w.ExecuteCommand = function(command){
+ return this.postMessage({
+ type: 'cmd',
+ command: command
+ });
};
- if (snapshot) {
- parts = ss.DecodeSpreadsheetSave(snapshot);
- }
- if (parts != null && parts.sheet) {
- ss.sheet.ResetSheet();
- ss.ParseSheetSave(snapshot.substring(parts.sheet.start, parts.sheet.end));
- }
- cmdstr = (function(){
- var i$, ref$, len$, results$ = [];
- for (i$ = 0, len$ = (ref$ = log).length; i$ < len$; ++i$) {
- line = ref$[i$];
- if (!/^re(calc|display)$/.test(line)) {
- results$.push(line);
- }
- }
- return results$;
- }()).join('\n');
- if (cmdstr.length) {
- cmdstr += "\n";
- }
- ss.context.sheetobj.ScheduleSheetCommands("set sheet defaulttextvalueformat text-wiki\n" + cmdstr + "recalc\n", false, true);
- SocialCalc.document.createElement = function(it){
- return new Node(it);
+ w.exportHTML = function(cb){
+ return w.thread.eval('window.ss.CreateSheetHTML()', function(err, html){
+ return cb(html);
+ });
+ };
+ w.exportCSV = function(cb){
+ return w.thread.eval('window.ss.SocialCalc.ConvertSaveToOtherFormat(\n window.ss.CreateSheetSave(), "csv"\n)', function(err, csv){
+ return cb(csv);
+ });
};
- return ss;
+ w.exportSave = function(cb){
+ return w.thread.eval('window.ss.CreateSheetSave()', function(err, save){
+ return cb(save);
+ });
+ };
+ return w;
};
return SC;
};
- Node = (function(){
- Node.displayName = 'Node';
- var prototype = Node.prototype, constructor = Node;
- function Node(tag, attrs, style, elems, raw){
- this.tag = tag != null ? tag : "div";
- this.attrs = attrs != null
- ? attrs
- : {};
- this.style = style != null
- ? style
- : {};
- this.elems = elems != null
- ? elems
- : [];
- this.raw = raw != null ? raw : '';
- }
- Object.defineProperty(prototype, 'id', {
- set: function(id){
- this.attrs.id = id;
- },
- configurable: true,
- enumerable: true
- });
- Object.defineProperty(prototype, 'width', {
- set: function(width){
- this.attrs.width = width;
- },
- configurable: true,
- enumerable: true
- });
- Object.defineProperty(prototype, 'height', {
- set: function(height){
- this.attrs.height = height;
- },
- configurable: true,
- enumerable: true
- });
- Object.defineProperty(prototype, 'className', {
- set: function($class){
- this.attrs['class'] = $class;
- },
- configurable: true,
- enumerable: true
- });
- Object.defineProperty(prototype, 'innerHTML', {
- set: function(raw){
- this.raw = raw;
- },
- get: function(){
- return this.raw || join$.call(this.elems.map(function(it){
- return it.outerHTML;
- }), "\n");
- },
- configurable: true,
- enumerable: true
- });
- Object.defineProperty(prototype, 'outerHTML', {
- get: function(){
- var tag, attrs, style, css, k, v;
- tag = this.tag, attrs = this.attrs, style = this.style;
- css = style.cssText || (function(){
- var ref$, results$ = [];
- for (k in ref$ = style) {
- v = ref$[k];
- results$.push(k + ":" + v);
- }
- return results$;
- }()).join(";");
- if (css) {
- attrs.style = css;
- } else {
- delete attrs.style;
- }
- return "<" + tag + (function(){
- var ref$, results$ = [];
- for (k in ref$ = attrs) {
- v = ref$[k];
- results$.push(" " + k + "=\"" + v + "\"");
- }
- return results$;
- }()).join("") + ">" + this.innerHTML + "</" + tag + ">";
- },
- configurable: true,
- enumerable: true
- });
- prototype.appendChild = function(it){
- return this.elems.push(it);
- };
- return Node;
- }());
}).call(this);
View
22 src/main.ls
@@ -51,8 +51,13 @@
{snapshot} <~ SC._get @params.room, IO
if snapshot
[type, content] = cb.call @params, snapshot
- @response.type type
- @response.send 200 content
+ if content instanceof Function
+ rv <~ content SC[@params.room]
+ @response.type type
+ @response.send 200 rv
+ else
+ @response.type type
+ @response.send 200 content
else
@response.type Text
@response.send 404 ''
@@ -64,13 +69,14 @@
JSON.stringify SC[@room].sheet.cells
]
@get '/_/:room/html': api -> [Html
- SC[@room]?CreateSheetHTML!
+ (sc, cb) ->
+ html <- sc.exportHTML
+ cb html
]
@get '/_/:room/csv': api -> [Csv
- SC[@room]?SocialCalc.ConvertSaveToOtherFormat(
- SC[@room]?CreateSheetSave!
- \csv
- )
+ (sc, cb) ->
+ csv <- sc.exportCSV
+ cb csv
]
@get '/_/:room': api -> [Text, it]
@@ -111,6 +117,7 @@
room = key.substr(5)
for client in IO.sockets.clients(key.substr(1))
| client.id isnt id => continue CleanRoom
+ SC[room]?terminate!
delete SC[room]
@on data: !->
@@ -155,6 +162,7 @@
| \stopHuddle
return if @KEY and KEY isnt @KEY
<~ DB.del <[ audit log chat ecell snapshot ]>.map -> "#it-#room"
+ SC[room]?terminate!
delete SC[room]
broadcast @data
| \ecell
View
219 src/sc.ls
@@ -6,6 +6,30 @@ bootSC = fs.readFileSync "#{
}/SocialCalcModule.js" \utf8
global.SC ?= {}
+##################################
+### WebWorker Threads Fallback ###
+##################################
+Worker = try
+ (require \webworker-threads).Worker
+catch => class => (code) ->
+ vm = require \vm
+ cxt = { console, self: { onmessage: -> } }
+ cxt.window =
+ setTimeout: (cb, ms) -> process.nextTick cb
+ clearTimeout: ->
+ @postMessage = (data) -> sandbox.self.onmessage {data}
+ @thread = cxt.thread =
+ nextTick: (cb) -> process.nextTick cb
+ eval: (src, cb) -> try
+ rv = vm.runInContext src, sandbox
+ cb? null, rv
+ catch e => cb? e
+ @terminate = ->
+ @sandbox = sandbox = vm.createContext cxt
+ sandbox.postMessage = (data) ~> @onmessage? {data}
+ vm.runInContext "(#code)()", sandbox
+##################################
+
@include = ->
DB = @include \db
@@ -31,86 +55,117 @@ global.SC ?= {}
if SC[room]?
SC[room]._doClearCache!
return SC[room]
- sandbox = vm.createContext {
- SocialCalc: null, ss: null, window: {
- setTimeout: (cb, ms) -> process.nextTick cb
- clearTimeout: ->
- }
- console
- }
- vm.runInContext bootSC, sandbox
- SocialCalc = sandbox.SocialCalc
- SocialCalc.SaveEditorSettings = -> ""
- SocialCalc.CreateAuditString = -> ""
- SocialCalc.CalculateEditorPositions = ->
- SocialCalc.Popup.Types.List.Create = ->
- SocialCalc.Popup.Types.ColorChooser.Create = ->
- SocialCalc.Popup.Initialize = ->
- vm.runInContext 'ss = new SocialCalc.SpreadsheetControl', sandbox
- SocialCalc.RecalcInfo.LoadSheet = (ref) ->
- ref = (ref - /[^a-zA-Z0-9]+/g).toLowerCase!
- if SC[ref]
- serialization = SC[ref].CreateSpreadsheetSave!
- parts = SC[ref].DecodeSpreadsheetSave serialization
- SocialCalc.RecalcLoadedSheet do
- ref
- serialization.substring parts.sheet.start, parts.sheet.end
- true # recalc
- else
- SocialCalc.RecalcLoadedSheet ref, '', true
- return true
-
- ss = sandbox.ss
- ss.SocialCalc = SocialCalc
- ss._room = room
- ss._doClearCache = -> SocialCalc.Formula.SheetCache.sheets = {}
- ss.editor.StatusCallback.EtherCalc = func: (editor, status, arg) ->
- return unless status is \doneposcalc and not ss.editor.busy
- newSnapshot = ss.CreateSpreadsheetSave!
- return if ss._snapshot is newSnapshot
- io.sockets.in "recalc.#room" .emit \data {
- type: \recalc
- snapshot: newSnapshot
- force: true
- room
- }
- ss._snapshot = newSnapshot
- <~ DB.multi!
- .set "snapshot-#room" newSnapshot
- .del "log-#room"
- .bgsave!
- .exec!
- console.log "==> Regenerated snapshot for #room"
- parts = ss.DecodeSpreadsheetSave(snapshot) if snapshot
- if parts?sheet
- ss.sheet.ResetSheet!
- ss.ParseSheetSave snapshot.substring parts.sheet.start, parts.sheet.end
- cmdstr = [ line for line in log
- | not /^re(calc|display)$/.test(line) ] * \\n
- cmdstr += "\n" if cmdstr.length
- ss.context.sheetobj.ScheduleSheetCommands "set sheet defaulttextvalueformat text-wiki\n#{
- cmdstr
- }recalc\n" false true
-
- # HTML Export support
- SocialCalc.document.createElement = -> new Node it
- return ss
+ w = new Worker ->
+ self.onmessage = ({ data: { type, ref, snapshot, command, room, log=[] } }) -> switch type
+ | \cmd
+ window.ss.ExecuteCommand command
+ | \recalc
+ SocialCalc.RecalcLoadedSheet ref, snapshot, true
+ | \clearCache
+ SocialCalc.Formula.SheetCache.sheets = {}
+ | \exportSave
+ postMessage { type: \save, save: window.ss.CreateSheetSave! }
+ | \exportHTML
+ postMessage { type: \html, html: window.ss.CreateSheetHTML! }
+ | \exportCSV
+ csv = window.ss.SocialCalc.ConvertSaveToOtherFormat(
+ window.ss.CreateSheetSave!
+ \csv
+ )
+ postMessage { type: \csv, csv }
+ | \init
+ SocialCalc.SaveEditorSettings = -> ""
+ SocialCalc.CreateAuditString = -> ""
+ SocialCalc.CalculateEditorPositions = ->
+ SocialCalc.Popup.Types.List.Create = ->
+ SocialCalc.Popup.Types.ColorChooser.Create = ->
+ SocialCalc.Popup.Initialize = ->
+ SocialCalc.RecalcInfo.LoadSheet = (ref) ->
+ ref = "#ref".replace(/[^a-zA-Z0-9]+/g '')toLowerCase!
+ postMessage { type: \load-sheet, ref }
+ return true
+ window.setTimeout = (cb, ms) -> thread.next-tick cb
+ window.clearTimeout = ->
+ window.ss = ss = new SocialCalc.SpreadsheetControl
+ ss.SocialCalc = SocialCalc
+ ss._room = room
+ parts = ss.DecodeSpreadsheetSave(snapshot) if snapshot
+ ss.editor.StatusCallback.EtherCalc = func: (editor, status, arg) ->
+ return unless status is \doneposcalc and not ss.editor.busy
+ newSnapshot = ss.CreateSpreadsheetSave!
+ return if ss._snapshot is newSnapshot
+ ss._snapshot = newSnapshot
+ postMessage { type: \snapshot, snapshot: newSnapshot }
+ if parts?sheet
+ ss.sheet.ResetSheet!
+ ss.ParseSheetSave snapshot.substring parts.sheet.start, parts.sheet.end
+ cmdstr = [ line for line in log
+ | not /^re(calc|display)$/.test(line) ].join("\n")
+ cmdstr += "\n" if cmdstr.length
+ ss.context.sheetobj.ScheduleSheetCommands "set sheet defaulttextvalueformat text-wiki\n#{
+ cmdstr
+ }recalc\n" false true
+ class Node
+ (@tag="div", @attrs={}, @style={}, @elems=[], @raw='')->
+ id: ~(@attrs.id)->
+ width: ~(@attrs.width)->
+ height: ~(@attrs.height)->
+ className: ~(@attrs.class)->
+ innerHTML: ~
+ (@raw)->
+ -> @raw or [e.outerHTML for e in @elems].join("\n")
+ outerHTML: ~->
+ {tag, attrs, style} = @
+ css = style.cssText or [ "#k:#v" for k, v of style ].join(";")
+ if css then attrs.style = css else delete attrs.style
+ return "<#tag#{
+ [ " #k=\"#v\"" for k, v of attrs ].join('')
+ }>#{ @innerHTML }</#tag>"
+ appendChild: -> @elems.push it
+ SocialCalc.document.createElement = -> new Node it
+ w._snapshot = snapshot
+ w.thread.eval bootSC
+ w.postMessage { type: \init, room, log, snapshot }
+ w.on-snapshot = (newSnapshot) ->
+ io.sockets.in "recalc.#room" .emit \data {
+ type: \recalc
+ snapshot: newSnapshot
+ force: true
+ room
+ }
+ w._snapshot = newSnapshot
+ <~ DB.multi!
+ .set "snapshot-#room" newSnapshot
+ .del "log-#room"
+ .bgsave!
+ .exec!
+ console.log "==> Regenerated snapshot for #room"
+ w.onmessage = ({ data: { type, snapshot, html, csv, ref, parts, save } }) -> switch type
+ | \snapshot => w.on-snapshot snapshot
+ | \save => w.on-save save
+ | \html => w.on-html html
+ | \csv => w.on-csv csv
+ | \load-sheet
+ <- SC._get ref, io
+ if SC[ref]
+ save <- SC[ref]exportSave
+ w.postMessage { type: \recalc, ref, snapshot: save }
+ else
+ w.postMessage { type: \recalc, ref, snapshot: '' }
+ w._doClearCache = -> @postMessage { type: \clearCache }
+ w.ExecuteCommand = (command) -> @postMessage { type: \cmd, command }
+ w.exportHTML = (cb) ->
+ err, html <- w.thread.eval 'window.ss.CreateSheetHTML()'
+ cb html
+ w.exportCSV = (cb) ->
+ err, csv <- w.thread.eval '''
+ window.ss.SocialCalc.ConvertSaveToOtherFormat(
+ window.ss.CreateSheetSave(), "csv"
+ )
+ '''
+ cb csv
+ w.exportSave = (cb) ->
+ err, save <- w.thread.eval 'window.ss.CreateSheetSave()'
+ cb save
+ return w
return SC
-
-class Node
- (@tag="div", @attrs={}, @style={}, @elems=[], @raw='')->
- id: ~(@attrs.id)->
- width: ~(@attrs.width)->
- height: ~(@attrs.height)->
- className: ~(@attrs.class)->
- innerHTML: ~
- (@raw)->
- -> @raw or (@elems.map -> it.outerHTML) * "\n"
- outerHTML: ~->
- {tag, attrs, style} = @
- css = style.cssText or [ "#k:#v" for k, v of style ] * ";"
- if css then attrs.style = css else delete attrs.style
- return "<#tag#{
- [ " #k=\"#v\"" for k, v of attrs ] * ""
- }>#{ @innerHTML }</#tag>"
- appendChild: -> @elems.push it

0 comments on commit 0f41d20

Please sign in to comment.
Something went wrong with that request. Please try again.