withsubclocks.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global.S = factory());
  5. }(this, (function () { 'use strict';
  6. // Public interface
  7. var S = function S(fn, value) {
  8. var owner = Owner, clock = RunningClock === null ? RootClock : RunningClock, running = RunningNode;
  9. if (owner === null)
  10. console.warn("computations created without a root or parent will never be disposed");
  11. var node = new ComputationNode(clock, fn, value);
  12. Owner = RunningNode = node;
  13. if (RunningClock === null) {
  14. toplevelComputation(node);
  15. }
  16. else {
  17. node.value = node.fn(node.value);
  18. }
  19. if (owner && owner !== UNOWNED) {
  20. if (owner.owned === null)
  21. owner.owned = [node];
  22. else
  23. owner.owned.push(node);
  24. }
  25. Owner = owner;
  26. RunningNode = running;
  27. return function computation() {
  28. if (RunningNode !== null) {
  29. var rclock = RunningClock, sclock = node.clock;
  30. while (rclock.depth > sclock.depth + 1)
  31. rclock = rclock.parent;
  32. if (rclock === sclock || rclock.parent === sclock) {
  33. if (node.preclocks !== null) {
  34. for (var i = 0; i < node.preclocks.count; i++) {
  35. var preclock = node.preclocks.clocks[i];
  36. updateClock(preclock);
  37. }
  38. }
  39. if (node.age === node.clock.time()) {
  40. if (node.state === RUNNING)
  41. throw new Error("circular dependency");
  42. else
  43. updateNode(node); // checks for state === STALE internally, so don't need to check here
  44. }
  45. if (node.preclocks !== null) {
  46. for (var i = 0; i < node.preclocks.count; i++) {
  47. var preclock = node.preclocks.clocks[i];
  48. if (rclock === sclock)
  49. logNodePreClock(preclock, RunningNode);
  50. else
  51. logClockPreClock(preclock, rclock, RunningNode);
  52. }
  53. }
  54. }
  55. else {
  56. if (rclock.depth > sclock.depth)
  57. rclock = rclock.parent;
  58. while (sclock.depth > rclock.depth + 1)
  59. sclock = sclock.parent;
  60. if (sclock.parent === rclock) {
  61. logNodePreClock(sclock, RunningNode);
  62. }
  63. else {
  64. if (sclock.depth > rclock.depth)
  65. sclock = sclock.parent;
  66. while (rclock.parent !== sclock.parent)
  67. rclock = rclock.parent, sclock = sclock.parent;
  68. logClockPreClock(sclock, rclock, RunningNode);
  69. }
  70. updateClock(sclock);
  71. }
  72. logComputationRead(node, RunningNode);
  73. }
  74. return node.value;
  75. };
  76. };
  77. // compatibility with commonjs systems that expect default export to be at require('s.js').default rather than just require('s-js')
  78. Object.defineProperty(S, 'default', { value: S });
  79. S.root = function root(fn) {
  80. var owner = Owner, root = fn.length === 0 ? UNOWNED : new ComputationNode(RunningClock || RootClock, null, null), result = undefined, disposer = fn.length === 0 ? null : function _dispose() {
  81. if (RunningClock !== null) {
  82. markClockStale(root.clock);
  83. root.clock.disposes.add(root);
  84. }
  85. else {
  86. dispose(root);
  87. }
  88. };
  89. Owner = root;
  90. if (RunningClock === null) {
  91. result = topLevelRoot(fn, disposer, owner);
  92. }
  93. else {
  94. result = disposer === null ? fn() : fn(disposer);
  95. Owner = owner;
  96. }
  97. return result;
  98. };
  99. function topLevelRoot(fn, disposer, owner) {
  100. try {
  101. return disposer === null ? fn() : fn(disposer);
  102. }
  103. finally {
  104. Owner = owner;
  105. }
  106. }
  107. S.on = function on(ev, fn, seed, onchanges) {
  108. if (Array.isArray(ev))
  109. ev = callAll(ev);
  110. onchanges = !!onchanges;
  111. return S(on, seed);
  112. function on(value) {
  113. var running = RunningNode;
  114. ev();
  115. if (onchanges)
  116. onchanges = false;
  117. else {
  118. RunningNode = null;
  119. value = fn(value);
  120. RunningNode = running;
  121. }
  122. return value;
  123. }
  124. };
  125. function callAll(ss) {
  126. return function all() {
  127. for (var i = 0; i < ss.length; i++)
  128. ss[i]();
  129. };
  130. }
  131. S.data = function data(value) {
  132. var node = new DataNode(RunningClock === null ? RootClock : RunningClock, value);
  133. return function data(value) {
  134. var rclock = RunningClock, sclock = node.clock;
  135. if (RunningClock !== null) {
  136. while (rclock.depth > sclock.depth)
  137. rclock = rclock.parent;
  138. while (sclock.depth > rclock.depth && sclock.parent !== rclock)
  139. sclock = sclock.parent;
  140. if (sclock.parent !== rclock)
  141. while (rclock.parent !== sclock.parent)
  142. rclock = rclock.parent, sclock = sclock.parent;
  143. if (rclock !== sclock) {
  144. updateClock(sclock);
  145. }
  146. }
  147. var cclock = rclock === sclock ? sclock : sclock.parent;
  148. if (arguments.length > 0) {
  149. if (RunningClock !== null) {
  150. if (node.pending !== NOTPENDING) { // value has already been set once, check for conflicts
  151. if (value !== node.pending) {
  152. throw new Error("conflicting changes: " + value + " !== " + node.pending);
  153. }
  154. }
  155. else { // add to list of changes
  156. markClockStale(cclock);
  157. node.pending = value;
  158. cclock.changes.add(node);
  159. }
  160. }
  161. else { // not batching, respond to change now
  162. if (node.log !== null) {
  163. node.pending = value;
  164. RootClock.changes.add(node);
  165. event();
  166. }
  167. else {
  168. node.value = value;
  169. }
  170. }
  171. return value;
  172. }
  173. else {
  174. if (RunningNode !== null) {
  175. logDataRead(node, RunningNode);
  176. if (sclock.parent === rclock)
  177. logNodePreClock(sclock, RunningNode);
  178. else if (sclock !== rclock)
  179. logClockPreClock(sclock, rclock, RunningNode);
  180. }
  181. return node.value;
  182. }
  183. };
  184. };
  185. S.value = function value(current, eq) {
  186. var data = S.data(current), clock = RunningClock || RootClock, age = -1;
  187. return function value(update) {
  188. if (arguments.length === 0) {
  189. return data();
  190. }
  191. else {
  192. var same = eq ? eq(current, update) : current === update;
  193. if (!same) {
  194. var time = clock.time();
  195. if (age === time)
  196. throw new Error("conflicting values: " + update + " is not the same as " + current);
  197. age = time;
  198. current = update;
  199. data(update);
  200. }
  201. return update;
  202. }
  203. };
  204. };
  205. S.freeze = function freeze(fn) {
  206. var result = undefined;
  207. if (RunningClock !== null) {
  208. result = fn();
  209. }
  210. else {
  211. RunningClock = RootClock;
  212. RunningClock.changes.reset();
  213. try {
  214. result = fn();
  215. event();
  216. }
  217. finally {
  218. RunningClock = null;
  219. }
  220. }
  221. return result;
  222. };
  223. S.sample = function sample(fn) {
  224. var result, running = RunningNode;
  225. if (running !== null) {
  226. RunningNode = null;
  227. result = fn();
  228. RunningNode = running;
  229. }
  230. else {
  231. result = fn();
  232. }
  233. return result;
  234. };
  235. S.cleanup = function cleanup(fn) {
  236. if (Owner !== null) {
  237. if (Owner.cleanups === null)
  238. Owner.cleanups = [fn];
  239. else
  240. Owner.cleanups.push(fn);
  241. }
  242. else {
  243. console.warn("cleanups created without a root or parent will never be run");
  244. }
  245. };
  246. S.subclock = function subclock(fn) {
  247. var clock = new Clock(RunningClock || RootClock);
  248. return fn === undefined ? subclock : subclock(fn);
  249. function subclock(fn) {
  250. var result = null, running = RunningClock;
  251. RunningClock = clock;
  252. clock.state = STALE;
  253. try {
  254. result = fn();
  255. clock.subtime++;
  256. run(clock);
  257. }
  258. finally {
  259. RunningClock = running;
  260. }
  261. // if we were run from top level, have to flush any changes in RootClock
  262. if (RunningClock === null)
  263. event();
  264. return result;
  265. }
  266. };
  267. // Internal implementation
  268. /// Graph classes and operations
  269. var Clock = /** @class */ (function () {
  270. function Clock(parent) {
  271. this.parent = parent;
  272. this.id = Clock.count++;
  273. this.state = CURRENT;
  274. this.subtime = 0;
  275. this.preclocks = null;
  276. this.changes = new Queue(); // batched changes to data nodes
  277. this.subclocks = new Queue(); // subclocks that need to be updated
  278. this.updates = new Queue(); // computations to update
  279. this.disposes = new Queue(); // disposals to run after current batch of updates finishes
  280. if (parent !== null) {
  281. this.age = parent.time();
  282. this.depth = parent.depth + 1;
  283. }
  284. else {
  285. this.age = 0;
  286. this.depth = 0;
  287. }
  288. }
  289. Clock.prototype.time = function () {
  290. var time = this.subtime, p = this;
  291. while ((p = p.parent) !== null)
  292. time += p.subtime;
  293. return time;
  294. };
  295. Clock.count = 0;
  296. return Clock;
  297. }());
  298. var DataNode = /** @class */ (function () {
  299. function DataNode(clock, value) {
  300. this.clock = clock;
  301. this.value = value;
  302. this.pending = NOTPENDING;
  303. this.log = null;
  304. }
  305. return DataNode;
  306. }());
  307. var ComputationNode = /** @class */ (function () {
  308. function ComputationNode(clock, fn, value) {
  309. this.clock = clock;
  310. this.fn = fn;
  311. this.value = value;
  312. this.state = CURRENT;
  313. this.source1 = null;
  314. this.source1slot = 0;
  315. this.sources = null;
  316. this.sourceslots = null;
  317. this.log = null;
  318. this.preclocks = null;
  319. this.owned = null;
  320. this.cleanups = null;
  321. this.age = this.clock.time();
  322. }
  323. return ComputationNode;
  324. }());
  325. var Log = /** @class */ (function () {
  326. function Log() {
  327. this.node1 = null;
  328. this.node1slot = 0;
  329. this.nodes = null;
  330. this.nodeslots = null;
  331. }
  332. return Log;
  333. }());
  334. var NodePreClockLog = /** @class */ (function () {
  335. function NodePreClockLog() {
  336. this.count = 0;
  337. this.clocks = []; // [clock], where clock.parent === node.clock
  338. this.ages = []; // clock.id -> node.age
  339. this.ucount = 0; // number of ancestor clocks with preclocks from this node
  340. this.uclocks = [];
  341. this.uclockids = [];
  342. }
  343. return NodePreClockLog;
  344. }());
  345. var ClockPreClockLog = /** @class */ (function () {
  346. function ClockPreClockLog() {
  347. this.count = 0;
  348. this.clockcounts = []; // clock.id -> ref count
  349. this.clocks = []; // clock.id -> clock
  350. this.ids = []; // [clock.id]
  351. }
  352. return ClockPreClockLog;
  353. }());
  354. var Queue = /** @class */ (function () {
  355. function Queue() {
  356. this.items = [];
  357. this.count = 0;
  358. }
  359. Queue.prototype.reset = function () {
  360. this.count = 0;
  361. };
  362. Queue.prototype.add = function (item) {
  363. this.items[this.count++] = item;
  364. };
  365. Queue.prototype.run = function (fn) {
  366. var items = this.items;
  367. for (var i = 0; i < this.count; i++) {
  368. fn(items[i]);
  369. items[i] = null;
  370. }
  371. this.count = 0;
  372. };
  373. return Queue;
  374. }());
  375. // Constants
  376. var NOTPENDING = {}, CURRENT = 0, STALE = 1, RUNNING = 2;
  377. // "Globals" used to keep track of current system state
  378. var RootClock = new Clock(null), RunningClock = null, // currently running clock
  379. RunningNode = null, // currently running computation
  380. Owner = null, // owner for new computations
  381. UNOWNED = new ComputationNode(RootClock, null, null);
  382. // Functions
  383. function logRead(from, to) {
  384. var fromslot, toslot = to.source1 === null ? -1 : to.sources === null ? 0 : to.sources.length;
  385. if (from.node1 === null) {
  386. from.node1 = to;
  387. from.node1slot = toslot;
  388. fromslot = -1;
  389. }
  390. else if (from.nodes === null) {
  391. from.nodes = [to];
  392. from.nodeslots = [toslot];
  393. fromslot = 0;
  394. }
  395. else {
  396. fromslot = from.nodes.length;
  397. from.nodes.push(to);
  398. from.nodeslots.push(toslot);
  399. }
  400. if (to.source1 === null) {
  401. to.source1 = from;
  402. to.source1slot = fromslot;
  403. }
  404. else if (to.sources === null) {
  405. to.sources = [from];
  406. to.sourceslots = [fromslot];
  407. }
  408. else {
  409. to.sources.push(from);
  410. to.sourceslots.push(fromslot);
  411. }
  412. }
  413. function logDataRead(data, to) {
  414. if (data.log === null)
  415. data.log = new Log();
  416. logRead(data.log, to);
  417. }
  418. function logComputationRead(node, to) {
  419. if (node.log === null)
  420. node.log = new Log();
  421. logRead(node.log, to);
  422. }
  423. function logNodePreClock(clock, to) {
  424. if (to.preclocks === null)
  425. to.preclocks = new NodePreClockLog();
  426. else if (to.preclocks.ages[clock.id] === to.age)
  427. return;
  428. to.preclocks.ages[clock.id] = to.age;
  429. to.preclocks.clocks[to.preclocks.count++] = clock;
  430. }
  431. function logClockPreClock(sclock, rclock, rnode) {
  432. var clocklog = rclock.preclocks === null ? (rclock.preclocks = new ClockPreClockLog()) : rclock.preclocks, nodelog = rnode.preclocks === null ? (rnode.preclocks = new NodePreClockLog()) : rnode.preclocks;
  433. if (nodelog.ages[sclock.id] === rnode.age)
  434. return;
  435. nodelog.ages[sclock.id] = rnode.age;
  436. nodelog.uclocks[nodelog.ucount] = rclock;
  437. nodelog.uclockids[nodelog.ucount++] = sclock.id;
  438. var clockcount = clocklog.clockcounts[sclock.id];
  439. if (clockcount === undefined) {
  440. clocklog.ids[clocklog.count++] = sclock.id;
  441. clocklog.clockcounts[sclock.id] = 1;
  442. clocklog.clocks[sclock.id] = sclock;
  443. }
  444. else if (clockcount === 0) {
  445. clocklog.clockcounts[sclock.id] = 1;
  446. clocklog.clocks[sclock.id] = sclock;
  447. }
  448. else {
  449. clocklog.clockcounts[sclock.id]++;
  450. }
  451. }
  452. function event() {
  453. // b/c we might be under a top level S.root(), have to preserve current root
  454. var owner = Owner;
  455. RootClock.subclocks.reset();
  456. RootClock.updates.reset();
  457. RootClock.subtime++;
  458. try {
  459. run(RootClock);
  460. }
  461. finally {
  462. RunningClock = RunningNode = null;
  463. Owner = owner;
  464. }
  465. }
  466. function toplevelComputation(node) {
  467. RunningClock = RootClock;
  468. RootClock.changes.reset();
  469. RootClock.subclocks.reset();
  470. RootClock.updates.reset();
  471. try {
  472. node.value = node.fn(node.value);
  473. if (RootClock.changes.count > 0 || RootClock.subclocks.count > 0 || RootClock.updates.count > 0) {
  474. RootClock.subtime++;
  475. run(RootClock);
  476. }
  477. }
  478. finally {
  479. RunningClock = Owner = RunningNode = null;
  480. }
  481. }
  482. function run(clock) {
  483. var running = RunningClock, count = 0;
  484. RunningClock = clock;
  485. clock.disposes.reset();
  486. // for each batch ...
  487. while (clock.changes.count !== 0 || clock.subclocks.count !== 0 || clock.updates.count !== 0 || clock.disposes.count !== 0) {
  488. if (count > 0) // don't tick on first run, or else we expire already scheduled updates
  489. clock.subtime++;
  490. clock.changes.run(applyDataChange);
  491. clock.subclocks.run(updateClock);
  492. clock.updates.run(updateNode);
  493. clock.disposes.run(dispose);
  494. // if there are still changes after excessive batches, assume runaway
  495. if (count++ > 1e5) {
  496. throw new Error("Runaway clock detected");
  497. }
  498. }
  499. RunningClock = running;
  500. }
  501. function applyDataChange(data) {
  502. data.value = data.pending;
  503. data.pending = NOTPENDING;
  504. if (data.log)
  505. markComputationsStale(data.log);
  506. }
  507. function markComputationsStale(log) {
  508. var node1 = log.node1, nodes = log.nodes;
  509. // mark all downstream nodes stale which haven't been already
  510. if (node1 !== null)
  511. markNodeStale(node1);
  512. if (nodes !== null) {
  513. for (var i = 0, len = nodes.length; i < len; i++) {
  514. markNodeStale(nodes[i]);
  515. }
  516. }
  517. }
  518. function markNodeStale(node) {
  519. var time = node.clock.time();
  520. if (node.age < time) {
  521. markClockStale(node.clock);
  522. node.age = time;
  523. node.state = STALE;
  524. node.clock.updates.add(node);
  525. if (node.owned !== null)
  526. markOwnedNodesForDisposal(node.owned);
  527. if (node.log !== null)
  528. markComputationsStale(node.log);
  529. }
  530. }
  531. function markOwnedNodesForDisposal(owned) {
  532. for (var i = 0; i < owned.length; i++) {
  533. var child = owned[i];
  534. child.age = child.clock.time();
  535. child.state = CURRENT;
  536. if (child.owned !== null)
  537. markOwnedNodesForDisposal(child.owned);
  538. }
  539. }
  540. function markClockStale(clock) {
  541. var time = 0;
  542. if ((clock.parent !== null && clock.age < (time = clock.parent.time())) || clock.state === CURRENT) {
  543. if (clock.parent !== null) {
  544. clock.age = time;
  545. markClockStale(clock.parent);
  546. clock.parent.subclocks.add(clock);
  547. }
  548. clock.changes.reset();
  549. clock.subclocks.reset();
  550. clock.updates.reset();
  551. clock.state = STALE;
  552. }
  553. }
  554. function updateClock(clock) {
  555. var time = clock.parent.time();
  556. if (clock.age < time || clock.state === STALE) {
  557. if (clock.age < time)
  558. clock.state = CURRENT;
  559. if (clock.preclocks !== null) {
  560. for (var i = 0; i < clock.preclocks.ids.length; i++) {
  561. var preclock = clock.preclocks.clocks[clock.preclocks.ids[i]];
  562. if (preclock)
  563. updateClock(preclock);
  564. }
  565. }
  566. clock.age = time;
  567. }
  568. if (clock.state === RUNNING) {
  569. throw new Error("clock circular reference");
  570. }
  571. else if (clock.state === STALE) {
  572. clock.state = RUNNING;
  573. run(clock);
  574. clock.state = CURRENT;
  575. }
  576. }
  577. function updateNode(node) {
  578. if (node.state === STALE) {
  579. var owner = Owner, running = RunningNode, clock = RunningClock;
  580. Owner = RunningNode = node;
  581. RunningClock = node.clock;
  582. node.state = RUNNING;
  583. cleanup(node, false);
  584. node.value = node.fn(node.value);
  585. node.state = CURRENT;
  586. Owner = owner;
  587. RunningNode = running;
  588. RunningClock = clock;
  589. }
  590. }
  591. function cleanup(node, final) {
  592. var source1 = node.source1, sources = node.sources, sourceslots = node.sourceslots, cleanups = node.cleanups, owned = node.owned, preclocks = node.preclocks, i, len;
  593. if (cleanups !== null) {
  594. for (i = 0; i < cleanups.length; i++) {
  595. cleanups[i](final);
  596. }
  597. node.cleanups = null;
  598. }
  599. if (owned !== null) {
  600. for (i = 0; i < owned.length; i++) {
  601. dispose(owned[i]);
  602. }
  603. node.owned = null;
  604. }
  605. if (source1 !== null) {
  606. cleanupSource(source1, node.source1slot);
  607. node.source1 = null;
  608. }
  609. if (sources !== null) {
  610. for (i = 0, len = sources.length; i < len; i++) {
  611. cleanupSource(sources.pop(), sourceslots.pop());
  612. }
  613. }
  614. if (preclocks !== null) {
  615. for (i = 0; i < preclocks.count; i++) {
  616. preclocks.clocks[i] = null;
  617. }
  618. preclocks.count = 0;
  619. for (i = 0; i < preclocks.ucount; i++) {
  620. var upreclocks = preclocks.uclocks[i].preclocks, uclockid = preclocks.uclockids[i];
  621. if (--upreclocks.clockcounts[uclockid] === 0) {
  622. upreclocks.clocks[uclockid] = null;
  623. }
  624. }
  625. preclocks.ucount = 0;
  626. }
  627. }
  628. function cleanupSource(source, slot) {
  629. var nodes = source.nodes, nodeslots = source.nodeslots, last, lastslot;
  630. if (slot === -1) {
  631. source.node1 = null;
  632. }
  633. else {
  634. last = nodes.pop();
  635. lastslot = nodeslots.pop();
  636. if (slot !== nodes.length) {
  637. nodes[slot] = last;
  638. nodeslots[slot] = lastslot;
  639. if (lastslot === -1) {
  640. last.source1slot = slot;
  641. }
  642. else {
  643. last.sourceslots[lastslot] = slot;
  644. }
  645. }
  646. }
  647. }
  648. function dispose(node) {
  649. node.clock = null;
  650. node.fn = null;
  651. node.log = null;
  652. node.preclocks = null;
  653. cleanup(node, true);
  654. }
  655. return S;
  656. })));