Skip to content
This repository
Browse code

* 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...
commit 0f41d2032483388533ce9b62faa7902944efbb05 1 parent 7a82251
唐鳳 authored
36 main.js
@@ -86,8 +86,15 @@
86 86 snapshot = arg$.snapshot;
87 87 if (snapshot) {
88 88 ref$ = cb.call(this$.params, snapshot), type = ref$[0], content = ref$[1];
89   - this$.response.type(type);
90   - return this$.response.send(200, content);
  89 + if (content instanceof Function) {
  90 + return content(SC[this$.params.room], function(rv){
  91 + this$.response.type(type);
  92 + return this$.response.send(200, rv);
  93 + });
  94 + } else {
  95 + this$.response.type(type);
  96 + return this$.response.send(200, content);
  97 + }
91 98 } else {
92 99 this$.response.type(Text);
93 100 return this$.response.send(404, '');
@@ -107,14 +114,24 @@
107 114 });
108 115 this.get({
109 116 '/_/:room/html': api(function(){
110   - var ref$;
111   - return [Html, (ref$ = SC[this.room]) != null ? ref$.CreateSheetHTML() : void 8];
  117 + return [
  118 + Html, function(sc, cb){
  119 + return sc.exportHTML(function(html){
  120 + return cb(html);
  121 + });
  122 + }
  123 + ];
112 124 })
113 125 });
114 126 this.get({
115 127 '/_/:room/csv': api(function(){
116   - var ref$;
117   - return [Csv, (ref$ = SC[this.room]) != null ? ref$.SocialCalc.ConvertSaveToOtherFormat((ref$ = SC[this.room]) != null ? ref$.CreateSheetSave() : void 8, 'csv') : void 8];
  128 + return [
  129 + Csv, function(sc, cb){
  130 + return sc.exportCSV(function(csv){
  131 + return cb(csv);
  132 + });
  133 + }
  134 + ];
118 135 })
119 136 });
120 137 this.get({
@@ -189,6 +206,9 @@
189 206 continue CleanRoom;
190 207 }
191 208 }
  209 + if ((ref$ = SC[room]) != null) {
  210 + ref$.terminate();
  211 + }
192 212 delete SC[room];
193 213 }
194 214 }
@@ -275,6 +295,10 @@
275 295 DB.del(['audit', 'log', 'chat', 'ecell', 'snapshot'].map(function(it){
276 296 return it + "-" + room;
277 297 }), function(){
  298 + var ref$;
  299 + if ((ref$ = SC[room]) != null) {
  300 + ref$.terminate();
  301 + }
278 302 delete SC[room];
279 303 return broadcast(this$.data);
280 304 });
5 package.json
... ... @@ -1,7 +1,7 @@
1 1 {
2 2 "name": "ethercalc",
3 3 "description": "Multi-User Spreadsheet Server",
4   - "version": "0.20121111.0",
  4 + "version": "0.20121213.0",
5 5 "homepage": "http://ethercalc.net/",
6 6 "repository": {
7 7 "type": "git",
@@ -15,7 +15,8 @@
15 15 },
16 16 "optionalDependencies": {
17 17 "hiredis": "0.1.x",
18   - "LiveScript": "1.1.x"
  18 + "LiveScript": "1.1.x",
  19 + "webworkerThreads": "0.4.x"
19 20 },
20 21 "directories": {
21 22 "bin": "./bin"
4 package.ls 100644 → 100755
... ... @@ -1,6 +1,7 @@
  1 +#!/usr/bin/env lsc -cj
1 2 name: \ethercalc
2 3 description: 'Multi-User Spreadsheet Server'
3   -version: \0.20121111.0
  4 +version: \0.20121213.0
4 5 homepage: 'http://ethercalc.net/'
5 6 repository:
6 7 type: 'git'
@@ -13,6 +14,7 @@ dependencies:
13 14 optional-dependencies:
14 15 hiredis: \0.1.x
15 16 LiveScript: \1.1.x
  17 + webworker-threads: \0.4.x
16 18 directories:
17 19 bin: \./bin
18 20 subdomain: \ethercalc
499 sc.js
... ... @@ -1,10 +1,65 @@
1 1 (function(){
2   - var vm, fs, path, bootSC, Node, replace$ = ''.replace, join$ = [].join;
  2 + var vm, fs, path, bootSC, Worker, e;
3 3 vm = require('vm');
4 4 fs = require('fs');
5 5 path = require('path');
6 6 bootSC = fs.readFileSync(path.dirname(fs.realpathSync(__filename)) + "/SocialCalcModule.js", 'utf8');
7 7 global.SC == null && (global.SC = {});
  8 + Worker = (function(){
  9 + try {
  10 + return require('xebworker-threads').Worker;
  11 + } catch (e$) {
  12 + e = e$;
  13 + return (function(){
  14 + var prototype = constructor.prototype;
  15 + function constructor(code){
  16 + var vm, cxt, sandbox, this$ = this;
  17 + vm = require('vm');
  18 + cxt = {
  19 + console: console,
  20 + self: {
  21 + onmessage: function(){}
  22 + }
  23 + };
  24 + cxt.window = {
  25 + setTimeout: function(cb, ms){
  26 + return process.nextTick(cb);
  27 + },
  28 + clearTimeout: function(){}
  29 + };
  30 + this.postMessage = function(data){
  31 + return sandbox.self.onmessage({
  32 + data: data
  33 + });
  34 + };
  35 + this.thread = cxt.thread = {
  36 + nextTick: function(cb){
  37 + return process.nextTick(cb);
  38 + },
  39 + eval: function(src, cb){
  40 + var rv, e;
  41 + try {
  42 + rv = vm.runInContext(src, sandbox);
  43 + return typeof cb === 'function' ? cb(null, rv) : void 8;
  44 + } catch (e$) {
  45 + e = e$;
  46 + return typeof cb === 'function' ? cb(e) : void 8;
  47 + }
  48 + }
  49 + };
  50 + this.terminate = function(){};
  51 + this.sandbox = sandbox = vm.createContext(cxt);
  52 + sandbox.postMessage = function(data){
  53 + return typeof this$.onmessage === 'function' ? this$.onmessage({
  54 + data: data
  55 + }) : void 8;
  56 + };
  57 + vm.runInContext("(" + code + ")()", sandbox);
  58 + }
  59 + return constructor;
  60 + }());
  61 + }
  62 + }());
8 63 this.include = function(){
9 64 var DB;
10 65 DB = this.include('db');
@@ -37,192 +92,288 @@
37 92 });
38 93 };
39 94 SC._init = function(snapshot, log, DB, room, io){
40   - var sandbox, SocialCalc, ss, parts, cmdstr, line;
  95 + var w;
41 96 log == null && (log = []);
42 97 if (SC[room] != null) {
43 98 SC[room]._doClearCache();
44 99 return SC[room];
45 100 }
46   - sandbox = vm.createContext({
47   - SocialCalc: null,
48   - ss: null,
49   - window: {
50   - setTimeout: function(cb, ms){
51   - return process.nextTick(cb);
52   - },
53   - clearTimeout: function(){}
54   - },
55   - console: console
  101 + w = new Worker(function(){
  102 + return self.onmessage = function(arg$){
  103 + var ref$, type, ref, snapshot, command, room, log, ref1$, csv, ss, parts, cmdstr, line, Node;
  104 + ref$ = arg$.data, type = ref$.type, ref = ref$.ref, snapshot = ref$.snapshot, command = ref$.command, room = ref$.room, log = (ref1$ = ref$.log) != null
  105 + ? ref1$
  106 + : [];
  107 + switch (type) {
  108 + case 'cmd':
  109 + return window.ss.ExecuteCommand(command);
  110 + case 'recalc':
  111 + return SocialCalc.RecalcLoadedSheet(ref, snapshot, true);
  112 + case 'clearCache':
  113 + return SocialCalc.Formula.SheetCache.sheets = {};
  114 + case 'exportSave':
  115 + return postMessage({
  116 + type: 'save',
  117 + save: window.ss.CreateSheetSave()
  118 + });
  119 + case 'exportHTML':
  120 + return postMessage({
  121 + type: 'html',
  122 + html: window.ss.CreateSheetHTML()
  123 + });
  124 + case 'exportCSV':
  125 + csv = window.ss.SocialCalc.ConvertSaveToOtherFormat(window.ss.CreateSheetSave(), 'csv');
  126 + return postMessage({
  127 + type: 'csv',
  128 + csv: csv
  129 + });
  130 + case 'init':
  131 + SocialCalc.SaveEditorSettings = function(){
  132 + return "";
  133 + };
  134 + SocialCalc.CreateAuditString = function(){
  135 + return "";
  136 + };
  137 + SocialCalc.CalculateEditorPositions = function(){};
  138 + SocialCalc.Popup.Types.List.Create = function(){};
  139 + SocialCalc.Popup.Types.ColorChooser.Create = function(){};
  140 + SocialCalc.Popup.Initialize = function(){};
  141 + SocialCalc.RecalcInfo.LoadSheet = function(ref){
  142 + ref = (ref + "").replace(/[^a-zA-Z0-9]+/g, '').toLowerCase();
  143 + postMessage({
  144 + type: 'load-sheet',
  145 + ref: ref
  146 + });
  147 + return true;
  148 + };
  149 + window.setTimeout = function(cb, ms){
  150 + return thread.nextTick(cb);
  151 + };
  152 + window.clearTimeout = function(){};
  153 + window.ss = ss = new SocialCalc.SpreadsheetControl;
  154 + ss.SocialCalc = SocialCalc;
  155 + ss._room = room;
  156 + if (snapshot) {
  157 + parts = ss.DecodeSpreadsheetSave(snapshot);
  158 + }
  159 + ss.editor.StatusCallback.EtherCalc = {
  160 + func: function(editor, status, arg){
  161 + var newSnapshot;
  162 + if (!(status === 'doneposcalc' && !ss.editor.busy)) {
  163 + return;
  164 + }
  165 + newSnapshot = ss.CreateSpreadsheetSave();
  166 + if (ss._snapshot === newSnapshot) {
  167 + return;
  168 + }
  169 + ss._snapshot = newSnapshot;
  170 + return postMessage({
  171 + type: 'snapshot',
  172 + snapshot: newSnapshot
  173 + });
  174 + }
  175 + };
  176 + if (parts != null && parts.sheet) {
  177 + ss.sheet.ResetSheet();
  178 + ss.ParseSheetSave(snapshot.substring(parts.sheet.start, parts.sheet.end));
  179 + }
  180 + cmdstr = (function(){
  181 + var i$, ref$, len$, results$ = [];
  182 + for (i$ = 0, len$ = (ref$ = log).length; i$ < len$; ++i$) {
  183 + line = ref$[i$];
  184 + if (!/^re(calc|display)$/.test(line)) {
  185 + results$.push(line);
  186 + }
  187 + }
  188 + return results$;
  189 + }()).join("\n");
  190 + if (cmdstr.length) {
  191 + cmdstr += "\n";
  192 + }
  193 + ss.context.sheetobj.ScheduleSheetCommands("set sheet defaulttextvalueformat text-wiki\n" + cmdstr + "recalc\n", false, true);
  194 + Node = (function(){
  195 + Node.displayName = 'Node';
  196 + var prototype = Node.prototype, constructor = Node;
  197 + function Node(tag, attrs, style, elems, raw){
  198 + this.tag = tag != null ? tag : "div";
  199 + this.attrs = attrs != null
  200 + ? attrs
  201 + : {};
  202 + this.style = style != null
  203 + ? style
  204 + : {};
  205 + this.elems = elems != null
  206 + ? elems
  207 + : [];
  208 + this.raw = raw != null ? raw : '';
  209 + }
  210 + Object.defineProperty(prototype, 'id', {
  211 + set: function(id){
  212 + this.attrs.id = id;
  213 + },
  214 + configurable: true,
  215 + enumerable: true
  216 + });
  217 + Object.defineProperty(prototype, 'width', {
  218 + set: function(width){
  219 + this.attrs.width = width;
  220 + },
  221 + configurable: true,
  222 + enumerable: true
  223 + });
  224 + Object.defineProperty(prototype, 'height', {
  225 + set: function(height){
  226 + this.attrs.height = height;
  227 + },
  228 + configurable: true,
  229 + enumerable: true
  230 + });
  231 + Object.defineProperty(prototype, 'className', {
  232 + set: function($class){
  233 + this.attrs['class'] = $class;
  234 + },
  235 + configurable: true,
  236 + enumerable: true
  237 + });
  238 + Object.defineProperty(prototype, 'innerHTML', {
  239 + set: function(raw){
  240 + this.raw = raw;
  241 + },
  242 + get: function(){
  243 + var e;
  244 + return this.raw || (function(){
  245 + var i$, ref$, len$, results$ = [];
  246 + for (i$ = 0, len$ = (ref$ = this.elems).length; i$ < len$; ++i$) {
  247 + e = ref$[i$];
  248 + results$.push(e.outerHTML);
  249 + }
  250 + return results$;
  251 + }.call(this)).join("\n");
  252 + },
  253 + configurable: true,
  254 + enumerable: true
  255 + });
  256 + Object.defineProperty(prototype, 'outerHTML', {
  257 + get: function(){
  258 + var tag, attrs, style, css, k, v;
  259 + tag = this.tag, attrs = this.attrs, style = this.style;
  260 + css = style.cssText || (function(){
  261 + var ref$, results$ = [];
  262 + for (k in ref$ = style) {
  263 + v = ref$[k];
  264 + results$.push(k + ":" + v);
  265 + }
  266 + return results$;
  267 + }()).join(";");
  268 + if (css) {
  269 + attrs.style = css;
  270 + } else {
  271 + delete attrs.style;
  272 + }
  273 + return "<" + tag + (function(){
  274 + var ref$, results$ = [];
  275 + for (k in ref$ = attrs) {
  276 + v = ref$[k];
  277 + results$.push(" " + k + "=\"" + v + "\"");
  278 + }
  279 + return results$;
  280 + }()).join('') + ">" + this.innerHTML + "</" + tag + ">";
  281 + },
  282 + configurable: true,
  283 + enumerable: true
  284 + });
  285 + prototype.appendChild = function(it){
  286 + return this.elems.push(it);
  287 + };
  288 + return Node;
  289 + }());
  290 + return SocialCalc.document.createElement = function(it){
  291 + return new Node(it);
  292 + };
  293 + }
  294 + };
56 295 });
57   - vm.runInContext(bootSC, sandbox);
58   - SocialCalc = sandbox.SocialCalc;
59   - SocialCalc.SaveEditorSettings = function(){
60   - return "";
61   - };
62   - SocialCalc.CreateAuditString = function(){
63   - return "";
  296 + w._snapshot = snapshot;
  297 + w.thread.eval(bootSC);
  298 + w.postMessage({
  299 + type: 'init',
  300 + room: room,
  301 + log: log,
  302 + snapshot: snapshot
  303 + });
  304 + w.onSnapshot = function(newSnapshot){
  305 + var this$ = this;
  306 + io.sockets['in']("recalc." + room).emit('data', {
  307 + type: 'recalc',
  308 + snapshot: newSnapshot,
  309 + force: true,
  310 + room: room
  311 + });
  312 + w._snapshot = newSnapshot;
  313 + return DB.multi().set("snapshot-" + room, newSnapshot).del("log-" + room).bgsave().exec(function(){
  314 + return console.log("==> Regenerated snapshot for " + room);
  315 + });
64 316 };
65   - SocialCalc.CalculateEditorPositions = function(){};
66   - SocialCalc.Popup.Types.List.Create = function(){};
67   - SocialCalc.Popup.Types.ColorChooser.Create = function(){};
68   - SocialCalc.Popup.Initialize = function(){};
69   - vm.runInContext('ss = new SocialCalc.SpreadsheetControl', sandbox);
70   - SocialCalc.RecalcInfo.LoadSheet = function(ref){
71   - var serialization, parts;
72   - ref = (replace$.call(ref, /[^a-zA-Z0-9]+/g, '')).toLowerCase();
73   - if (SC[ref]) {
74   - serialization = SC[ref].CreateSpreadsheetSave();
75   - parts = SC[ref].DecodeSpreadsheetSave(serialization);
76   - SocialCalc.RecalcLoadedSheet(ref, serialization.substring(parts.sheet.start, parts.sheet.end), true);
77   - } else {
78   - SocialCalc.RecalcLoadedSheet(ref, '', true);
  317 + w.onmessage = function(arg$){
  318 + var ref$, type, snapshot, html, csv, ref, parts, save;
  319 + ref$ = arg$.data, type = ref$.type, snapshot = ref$.snapshot, html = ref$.html, csv = ref$.csv, ref = ref$.ref, parts = ref$.parts, save = ref$.save;
  320 + switch (type) {
  321 + case 'snapshot':
  322 + return w.onSnapshot(snapshot);
  323 + case 'save':
  324 + return w.onSave(save);
  325 + case 'html':
  326 + return w.onHtml(html);
  327 + case 'csv':
  328 + return w.onCsv(csv);
  329 + case 'load-sheet':
  330 + return SC._get(ref, io, function(){
  331 + if (SC[ref]) {
  332 + return SC[ref].exportSave(function(save){
  333 + return w.postMessage({
  334 + type: 'recalc',
  335 + ref: ref,
  336 + snapshot: save
  337 + });
  338 + });
  339 + } else {
  340 + return w.postMessage({
  341 + type: 'recalc',
  342 + ref: ref,
  343 + snapshot: ''
  344 + });
  345 + }
  346 + });
79 347 }
80   - return true;
81 348 };
82   - ss = sandbox.ss;
83   - ss.SocialCalc = SocialCalc;
84   - ss._room = room;
85   - ss._doClearCache = function(){
86   - return SocialCalc.Formula.SheetCache.sheets = {};
  349 + w._doClearCache = function(){
  350 + return this.postMessage({
  351 + type: 'clearCache'
  352 + });
87 353 };
88   - ss.editor.StatusCallback.EtherCalc = {
89   - func: function(editor, status, arg){
90   - var newSnapshot, this$ = this;
91   - if (!(status === 'doneposcalc' && !ss.editor.busy)) {
92   - return;
93   - }
94   - newSnapshot = ss.CreateSpreadsheetSave();
95   - if (ss._snapshot === newSnapshot) {
96   - return;
97   - }
98   - io.sockets['in']("recalc." + room).emit('data', {
99   - type: 'recalc',
100   - snapshot: newSnapshot,
101   - force: true,
102   - room: room
103   - });
104   - ss._snapshot = newSnapshot;
105   - return DB.multi().set("snapshot-" + room, newSnapshot).del("log-" + room).bgsave().exec(function(){
106   - return console.log("==> Regenerated snapshot for " + room);
107   - });
108   - }
  354 + w.ExecuteCommand = function(command){
  355 + return this.postMessage({
  356 + type: 'cmd',
  357 + command: command
  358 + });
109 359 };
110   - if (snapshot) {
111   - parts = ss.DecodeSpreadsheetSave(snapshot);
112   - }
113   - if (parts != null && parts.sheet) {
114   - ss.sheet.ResetSheet();
115   - ss.ParseSheetSave(snapshot.substring(parts.sheet.start, parts.sheet.end));
116   - }
117   - cmdstr = (function(){
118   - var i$, ref$, len$, results$ = [];
119   - for (i$ = 0, len$ = (ref$ = log).length; i$ < len$; ++i$) {
120   - line = ref$[i$];
121   - if (!/^re(calc|display)$/.test(line)) {
122   - results$.push(line);
123   - }
124   - }
125   - return results$;
126   - }()).join('\n');
127   - if (cmdstr.length) {
128   - cmdstr += "\n";
129   - }
130   - ss.context.sheetobj.ScheduleSheetCommands("set sheet defaulttextvalueformat text-wiki\n" + cmdstr + "recalc\n", false, true);
131   - SocialCalc.document.createElement = function(it){
132   - return new Node(it);
  360 + w.exportHTML = function(cb){
  361 + return w.thread.eval('window.ss.CreateSheetHTML()', function(err, html){
  362 + return cb(html);
  363 + });
  364 + };
  365 + w.exportCSV = function(cb){
  366 + return w.thread.eval('window.ss.SocialCalc.ConvertSaveToOtherFormat(\n window.ss.CreateSheetSave(), "csv"\n)', function(err, csv){
  367 + return cb(csv);
  368 + });
133 369 };
134   - return ss;
  370 + w.exportSave = function(cb){
  371 + return w.thread.eval('window.ss.CreateSheetSave()', function(err, save){
  372 + return cb(save);
  373 + });
  374 + };
  375 + return w;
135 376 };
136 377 return SC;
137 378 };
138   - Node = (function(){
139   - Node.displayName = 'Node';
140   - var prototype = Node.prototype, constructor = Node;
141   - function Node(tag, attrs, style, elems, raw){
142   - this.tag = tag != null ? tag : "div";
143   - this.attrs = attrs != null
144   - ? attrs
145   - : {};
146   - this.style = style != null
147   - ? style
148   - : {};
149   - this.elems = elems != null
150   - ? elems
151   - : [];
152   - this.raw = raw != null ? raw : '';
153   - }
154   - Object.defineProperty(prototype, 'id', {
155   - set: function(id){
156   - this.attrs.id = id;
157   - },
158   - configurable: true,
159   - enumerable: true
160   - });
161   - Object.defineProperty(prototype, 'width', {
162   - set: function(width){
163   - this.attrs.width = width;
164   - },
165   - configurable: true,
166   - enumerable: true
167   - });
168   - Object.defineProperty(prototype, 'height', {
169   - set: function(height){
170   - this.attrs.height = height;
171   - },
172   - configurable: true,
173   - enumerable: true
174   - });
175   - Object.defineProperty(prototype, 'className', {
176   - set: function($class){
177   - this.attrs['class'] = $class;
178   - },
179   - configurable: true,
180   - enumerable: true
181   - });
182   - Object.defineProperty(prototype, 'innerHTML', {
183   - set: function(raw){
184   - this.raw = raw;
185   - },
186   - get: function(){
187   - return this.raw || join$.call(this.elems.map(function(it){
188   - return it.outerHTML;
189   - }), "\n");
190   - },
191   - configurable: true,
192   - enumerable: true
193   - });
194   - Object.defineProperty(prototype, 'outerHTML', {
195   - get: function(){
196   - var tag, attrs, style, css, k, v;
197   - tag = this.tag, attrs = this.attrs, style = this.style;
198   - css = style.cssText || (function(){
199   - var ref$, results$ = [];
200   - for (k in ref$ = style) {
201   - v = ref$[k];
202   - results$.push(k + ":" + v);
203   - }
204   - return results$;
205   - }()).join(";");
206   - if (css) {
207   - attrs.style = css;
208   - } else {
209   - delete attrs.style;
210   - }
211   - return "<" + tag + (function(){
212   - var ref$, results$ = [];
213   - for (k in ref$ = attrs) {
214   - v = ref$[k];
215   - results$.push(" " + k + "=\"" + v + "\"");
216   - }
217   - return results$;
218   - }()).join("") + ">" + this.innerHTML + "</" + tag + ">";
219   - },
220   - configurable: true,
221   - enumerable: true
222   - });
223   - prototype.appendChild = function(it){
224   - return this.elems.push(it);
225   - };
226   - return Node;
227   - }());
228 379 }).call(this);
22 src/main.ls
@@ -51,8 +51,13 @@
51 51 {snapshot} <~ SC._get @params.room, IO
52 52 if snapshot
53 53 [type, content] = cb.call @params, snapshot
54   - @response.type type
55   - @response.send 200 content
  54 + if content instanceof Function
  55 + rv <~ content SC[@params.room]
  56 + @response.type type
  57 + @response.send 200 rv
  58 + else
  59 + @response.type type
  60 + @response.send 200 content
56 61 else
57 62 @response.type Text
58 63 @response.send 404 ''
@@ -64,13 +69,14 @@
64 69 JSON.stringify SC[@room].sheet.cells
65 70 ]
66 71 @get '/_/:room/html': api -> [Html
67   - SC[@room]?CreateSheetHTML!
  72 + (sc, cb) ->
  73 + html <- sc.exportHTML
  74 + cb html
68 75 ]
69 76 @get '/_/:room/csv': api -> [Csv
70   - SC[@room]?SocialCalc.ConvertSaveToOtherFormat(
71   - SC[@room]?CreateSheetSave!
72   - \csv
73   - )
  77 + (sc, cb) ->
  78 + csv <- sc.exportCSV
  79 + cb csv
74 80 ]
75 81 @get '/_/:room': api -> [Text, it]
76 82
@@ -111,6 +117,7 @@
111 117 room = key.substr(5)
112 118 for client in IO.sockets.clients(key.substr(1))
113 119 | client.id isnt id => continue CleanRoom
  120 + SC[room]?terminate!
114 121 delete SC[room]
115 122
116 123 @on data: !->
@@ -155,6 +162,7 @@
155 162 | \stopHuddle
156 163 return if @KEY and KEY isnt @KEY
157 164 <~ DB.del <[ audit log chat ecell snapshot ]>.map -> "#it-#room"
  165 + SC[room]?terminate!
158 166 delete SC[room]
159 167 broadcast @data
160 168 | \ecell
219 src/sc.ls
@@ -6,6 +6,30 @@ bootSC = fs.readFileSync "#{
6 6 }/SocialCalcModule.js" \utf8
7 7 global.SC ?= {}
8 8
  9 +##################################
  10 +### WebWorker Threads Fallback ###
  11 +##################################
  12 +Worker = try
  13 + (require \webworker-threads).Worker
  14 +catch => class => (code) ->
  15 + vm = require \vm
  16 + cxt = { console, self: { onmessage: -> } }
  17 + cxt.window =
  18 + setTimeout: (cb, ms) -> process.nextTick cb
  19 + clearTimeout: ->
  20 + @postMessage = (data) -> sandbox.self.onmessage {data}
  21 + @thread = cxt.thread =
  22 + nextTick: (cb) -> process.nextTick cb
  23 + eval: (src, cb) -> try
  24 + rv = vm.runInContext src, sandbox
  25 + cb? null, rv
  26 + catch e => cb? e
  27 + @terminate = ->
  28 + @sandbox = sandbox = vm.createContext cxt
  29 + sandbox.postMessage = (data) ~> @onmessage? {data}
  30 + vm.runInContext "(#code)()", sandbox
  31 +##################################
  32 +
9 33 @include = ->
10 34 DB = @include \db
11 35
@@ -31,86 +55,117 @@ global.SC ?= {}
31 55 if SC[room]?
32 56 SC[room]._doClearCache!
33 57 return SC[room]
34   - sandbox = vm.createContext {
35   - SocialCalc: null, ss: null, window: {
36   - setTimeout: (cb, ms) -> process.nextTick cb
37   - clearTimeout: ->
38   - }
39   - console
40   - }
41   - vm.runInContext bootSC, sandbox
42   - SocialCalc = sandbox.SocialCalc
43   - SocialCalc.SaveEditorSettings = -> ""
44   - SocialCalc.CreateAuditString = -> ""
45   - SocialCalc.CalculateEditorPositions = ->
46   - SocialCalc.Popup.Types.List.Create = ->
47   - SocialCalc.Popup.Types.ColorChooser.Create = ->
48   - SocialCalc.Popup.Initialize = ->
49   - vm.runInContext 'ss = new SocialCalc.SpreadsheetControl', sandbox
50   - SocialCalc.RecalcInfo.LoadSheet = (ref) ->
51   - ref = (ref - /[^a-zA-Z0-9]+/g).toLowerCase!
52   - if SC[ref]
53   - serialization = SC[ref].CreateSpreadsheetSave!
54   - parts = SC[ref].DecodeSpreadsheetSave serialization
55   - SocialCalc.RecalcLoadedSheet do
56   - ref
57   - serialization.substring parts.sheet.start, parts.sheet.end
58   - true # recalc
59   - else
60   - SocialCalc.RecalcLoadedSheet ref, '', true
61   - return true
62   -
63   - ss = sandbox.ss
64   - ss.SocialCalc = SocialCalc
65   - ss._room = room
66   - ss._doClearCache = -> SocialCalc.Formula.SheetCache.sheets = {}
67   - ss.editor.StatusCallback.EtherCalc = func: (editor, status, arg) ->
68   - return unless status is \doneposcalc and not ss.editor.busy
69   - newSnapshot = ss.CreateSpreadsheetSave!
70   - return if ss._snapshot is newSnapshot
71   - io.sockets.in "recalc.#room" .emit \data {
72   - type: \recalc
73   - snapshot: newSnapshot
74   - force: true
75   - room
76   - }
77   - ss._snapshot = newSnapshot
78   - <~ DB.multi!
79   - .set "snapshot-#room" newSnapshot
80   - .del "log-#room"
81   - .bgsave!
82   - .exec!
83   - console.log "==> Regenerated snapshot for #room"
84   - parts = ss.DecodeSpreadsheetSave(snapshot) if snapshot
85   - if parts?sheet
86   - ss.sheet.ResetSheet!
87   - ss.ParseSheetSave snapshot.substring parts.sheet.start, parts.sheet.end
88   - cmdstr = [ line for line in log
89   - | not /^re(calc|display)$/.test(line) ] * \\n
90   - cmdstr += "\n" if cmdstr.length
91   - ss.context.sheetobj.ScheduleSheetCommands "set sheet defaulttextvalueformat text-wiki\n#{
92   - cmdstr
93   - }recalc\n" false true
94   -
95   - # HTML Export support
96   - SocialCalc.document.createElement = -> new Node it
97   - return ss
  58 + w = new Worker ->
  59 + self.onmessage = ({ data: { type, ref, snapshot, command, room, log=[] } }) -> switch type
  60 + | \cmd
  61 + window.ss.ExecuteCommand command
  62 + | \recalc
  63 + SocialCalc.RecalcLoadedSheet ref, snapshot, true
  64 + | \clearCache
  65 + SocialCalc.Formula.SheetCache.sheets = {}
  66 + | \exportSave
  67 + postMessage { type: \save, save: window.ss.CreateSheetSave! }
  68 + | \exportHTML
  69 + postMessage { type: \html, html: window.ss.CreateSheetHTML! }
  70 + | \exportCSV
  71 + csv = window.ss.SocialCalc.ConvertSaveToOtherFormat(
  72 + window.ss.CreateSheetSave!
  73 + \csv
  74 + )
  75 + postMessage { type: \csv, csv }
  76 + | \init
  77 + SocialCalc.SaveEditorSettings = -> ""
  78 + SocialCalc.CreateAuditString = -> ""
  79 + SocialCalc.CalculateEditorPositions = ->
  80 + SocialCalc.Popup.Types.List.Create = ->
  81 + SocialCalc.Popup.Types.ColorChooser.Create = ->
  82 + SocialCalc.Popup.Initialize = ->
  83 + SocialCalc.RecalcInfo.LoadSheet = (ref) ->
  84 + ref = "#ref".replace(/[^a-zA-Z0-9]+/g '')toLowerCase!
  85 + postMessage { type: \load-sheet, ref }
  86 + return true
  87 + window.setTimeout = (cb, ms) -> thread.next-tick cb
  88 + window.clearTimeout = ->
  89 + window.ss = ss = new SocialCalc.SpreadsheetControl
  90 + ss.SocialCalc = SocialCalc
  91 + ss._room = room
  92 + parts = ss.DecodeSpreadsheetSave(snapshot) if snapshot
  93 + ss.editor.StatusCallback.EtherCalc = func: (editor, status, arg) ->
  94 + return unless status is \doneposcalc and not ss.editor.busy
  95 + newSnapshot = ss.CreateSpreadsheetSave!
  96 + return if ss._snapshot is newSnapshot
  97 + ss._snapshot = newSnapshot
  98 + postMessage { type: \snapshot, snapshot: newSnapshot }
  99 + if parts?sheet
  100 + ss.sheet.ResetSheet!
  101 + ss.ParseSheetSave snapshot.substring parts.sheet.start, parts.sheet.end
  102 + cmdstr = [ line for line in log
  103 + | not /^re(calc|display)$/.test(line) ].join("\n")
  104 + cmdstr += "\n" if cmdstr.length
  105 + ss.context.sheetobj.ScheduleSheetCommands "set sheet defaulttextvalueformat text-wiki\n#{
  106 + cmdstr
  107 + }recalc\n" false true
  108 + class Node
  109 + (@tag="div", @attrs={}, @style={}, @elems=[], @raw='')->
  110 + id: ~(@attrs.id)->
  111 + width: ~(@attrs.width)->
  112 + height: ~(@attrs.height)->
  113 + className: ~(@attrs.class)->
  114 + innerHTML: ~
  115 + (@raw)->
  116 + -> @raw or [e.outerHTML for e in @elems].join("\n")
  117 + outerHTML: ~->
  118 + {tag, attrs, style} = @
  119 + css = style.cssText or [ "#k:#v" for k, v of style ].join(";")
  120 + if css then attrs.style = css else delete attrs.style
  121 + return "<#tag#{
  122 + [ " #k=\"#v\"" for k, v of attrs ].join('')
  123 + }>#{ @innerHTML }</#tag>"
  124 + appendChild: -> @elems.push it
  125 + SocialCalc.document.createElement = -> new Node it
  126 + w._snapshot = snapshot
  127 + w.thread.eval bootSC
  128 + w.postMessage { type: \init, room, log, snapshot }
  129 + w.on-snapshot = (newSnapshot) ->
  130 + io.sockets.in "recalc.#room" .emit \data {
  131 + type: \recalc
  132 + snapshot: newSnapshot
  133 + force: true
  134 + room
  135 + }
  136 + w._snapshot = newSnapshot
  137 + <~ DB.multi!
  138 + .set "snapshot-#room" newSnapshot
  139 + .del "log-#room"
  140 + .bgsave!
  141 + .exec!
  142 + console.log "==> Regenerated snapshot for #room"
  143 + w.onmessage = ({ data: { type, snapshot, html, csv, ref, parts, save } }) -> switch type
  144 + | \snapshot => w.on-snapshot snapshot
  145 + | \save => w.on-save save
  146 + | \html => w.on-html html
  147 + | \csv => w.on-csv csv
  148 + | \load-sheet
  149 + <- SC._get ref, io
  150 + if SC[ref]
  151 + save <- SC[ref]exportSave
  152 + w.postMessage { type: \recalc, ref, snapshot: save }
  153 + else
  154 + w.postMessage { type: \recalc, ref, snapshot: '' }
  155 + w._doClearCache = -> @postMessage { type: \clearCache }
  156 + w.ExecuteCommand = (command) -> @postMessage { type: \cmd, command }
  157 + w.exportHTML = (cb) ->
  158 + err, html <- w.thread.eval 'window.ss.CreateSheetHTML()'
  159 + cb html
  160 + w.exportCSV = (cb) ->
  161 + err, csv <- w.thread.eval '''
  162 + window.ss.SocialCalc.ConvertSaveToOtherFormat(
  163 + window.ss.CreateSheetSave(), "csv"
  164 + )
  165 + '''
  166 + cb csv
  167 + w.exportSave = (cb) ->
  168 + err, save <- w.thread.eval 'window.ss.CreateSheetSave()'
  169 + cb save
  170 + return w
98 171 return SC
99   -
100   -class Node
101   - (@tag="div", @attrs={}, @style={}, @elems=[], @raw='')->
102   - id: ~(@attrs.id)->
103   - width: ~(@attrs.width)->
104   - height: ~(@attrs.height)->
105   - className: ~(@attrs.class)->
106   - innerHTML: ~
107   - (@raw)->
108   - -> @raw or (@elems.map -> it.outerHTML) * "\n"
109   - outerHTML: ~->
110   - {tag, attrs, style} = @
111   - css = style.cssText or [ "#k:#v" for k, v of style ] * ";"
112   - if css then attrs.style = css else delete attrs.style
113   - return "<#tag#{
114   - [ " #k=\"#v\"" for k, v of attrs ] * ""
115   - }>#{ @innerHTML }</#tag>"
116   - appendChild: -> @elems.push it

0 comments on commit 0f41d20

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