braid-text 0.0.27 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.js +121 -58
  2. package/package.json +1 -1
  3. package/server-demo.js +1 -1
  4. package/test.js +476 -0
package/index.js CHANGED
@@ -5,6 +5,7 @@ let fs = require("fs")
5
5
 
6
6
  let braid_text = {
7
7
  db_folder: './braid-text-db',
8
+ length_cache_size: 10,
8
9
  cache: {}
9
10
  }
10
11
 
@@ -252,12 +253,10 @@ braid_text.get = async (key, options) => {
252
253
  body: "",
253
254
  })
254
255
 
255
- updates = OpLog_get_patches(resource.doc.toBytes(), resource.doc.getOpsSince([]))
256
+ updates = dt_get_patches(resource.doc)
256
257
  } else {
257
258
  // Then start the subscription from the parents in options
258
- let local_version = OpLog_remote_to_local(resource.doc, options.parents || options.version)
259
-
260
- updates = OpLog_get_patches(resource.doc.getPatchSince(local_version), resource.doc.getOpsSince(local_version))
259
+ updates = dt_get_patches(resource.doc, options.parents || options.version)
261
260
  }
262
261
 
263
262
  for (let u of updates) {
@@ -303,10 +302,10 @@ braid_text.put = async (key, options) => {
303
302
  let parents = resource.doc.getRemoteVersion().map((x) => x.join("-")).sort()
304
303
  let og_parents = options_parents || parents
305
304
 
306
- let max_pos = count_code_points(v_eq(parents, og_parents) ?
307
- resource.doc.get() :
308
- dt_get(resource.doc, og_parents).get())
309
-
305
+ let max_pos = resource.length_cache.get('' + og_parents) ??
306
+ (v_eq(parents, og_parents) ? resource.doc.len() :
307
+ dt_get(resource.doc, og_parents).len())
308
+
310
309
  if (body != null) {
311
310
  patches = [{
312
311
  unit: 'text',
@@ -343,8 +342,7 @@ braid_text.put = async (key, options) => {
343
342
  if (!options.validate_already_seen_versions) return
344
343
 
345
344
  // if we have seen it already, make sure it's the same as before
346
- let local_version = OpLog_remote_to_local(resource.doc, og_parents)
347
- let updates = OpLog_get_patches(resource.doc.getPatchSince(local_version), resource.doc.getOpsSince(local_version))
345
+ let updates = dt_get_patches(resource.doc, og_parents)
348
346
 
349
347
  let seen = {}
350
348
  for (let u of updates) {
@@ -403,6 +401,10 @@ braid_text.put = async (key, options) => {
403
401
  }
404
402
  resource.actor_seqs[v[0]] = v[1]
405
403
 
404
+ resource.length_cache.put(`${v[0]}-${v[1]}`, patches.reduce((a, b) =>
405
+ a + (b.content.length ? b.content.length : -(b.range[1] - b.range[0])),
406
+ max_pos))
407
+
406
408
  v = `${v[0]}-${v[1] + 1 - change_count}`
407
409
 
408
410
  let ps = og_parents
@@ -415,7 +417,7 @@ braid_text.put = async (key, options) => {
415
417
  for (let p of patches) {
416
418
  // delete
417
419
  for (let i = p.range[0]; i < p.range[1]; i++) {
418
- bytes.push(OpLog_create_bytes(v, ps, p.range[1] - 1 + offset, null))
420
+ bytes.push(dt_create_bytes(v, ps, p.range[1] - 1 + offset, null))
419
421
  offset--
420
422
  ps = [v]
421
423
  v = decode_version(v)
@@ -424,7 +426,7 @@ braid_text.put = async (key, options) => {
424
426
  // insert
425
427
  for (let i = 0; i < p.content?.length ?? 0; i++) {
426
428
  let c = p.content[i]
427
- bytes.push(OpLog_create_bytes(v, ps, p.range[1] + offset, c))
429
+ bytes.push(dt_create_bytes(v, ps, p.range[1] + offset, c))
428
430
  offset++
429
431
  ps = [v]
430
432
  v = decode_version(v)
@@ -584,6 +586,8 @@ async function get_resource(key) {
584
586
 
585
587
  resource.val = resource.doc.get()
586
588
 
589
+ resource.length_cache = createSimpleCache(braid_text.length_cache_size)
590
+
587
591
  done(resource)
588
592
  })
589
593
  return await cache[key]
@@ -764,31 +768,30 @@ async function file_sync(key, process_delta, get_init) {
764
768
  //////////////////////////////////////////////////////////////////
765
769
  //////////////////////////////////////////////////////////////////
766
770
 
767
- function dt_get(doc, version) {
771
+ function dt_get(doc, version, agent = null) {
772
+ let bytes = doc.toBytes()
773
+ dt_get.last_doc = doc = Doc.fromBytes(bytes, agent)
774
+
775
+ let [_agents, versions, parentss] = dt_parse([...bytes])
776
+
768
777
  let frontier = {}
769
- version.forEach((x) => (frontier[x] = true))
778
+ version.forEach((x) => frontier[x] = true)
770
779
 
771
780
  let local_version = []
772
- let [agents, versions, parentss] = parseDT([...doc.toBytes()])
773
- for (let i = 0; i < versions.length; i++) {
774
- if (frontier[versions[i].join("-")]) {
775
- local_version.push(i)
776
- }
777
- }
778
- local_version = new Uint32Array(local_version)
781
+ for (let i = 0; i < versions.length; i++)
782
+ if (frontier[versions[i].join("-")]) local_version.push(i)
783
+ dt_get.last_local_version = local_version = new Uint32Array(local_version)
779
784
 
780
785
  let after_versions = {}
781
- let [_, after_versions_array, __] = parseDT([...doc.getPatchSince(local_version)])
786
+ let [_, after_versions_array, __] = dt_parse([...doc.getPatchSince(local_version)])
782
787
  for (let v of after_versions_array) after_versions[v.join("-")] = true
783
788
 
784
- let new_doc = new Doc()
789
+ let new_doc = new Doc(agent)
785
790
  let op_runs = doc.getOpsSince([])
791
+
786
792
  let i = 0
787
793
  op_runs.forEach((op_run) => {
788
- let parents = parentss[i].map((x) => x.join("-"))
789
- let start = op_run.start
790
- let end = start + 1
791
- let content = op_run.content?.[0]
794
+ if (op_run.content) op_run.content = [...op_run.content]
792
795
 
793
796
  let len = op_run.end - op_run.start
794
797
  let base_i = i
@@ -804,38 +807,48 @@ function dt_get(doc, version) {
804
807
  ) {
805
808
  for (; i < I; i++) {
806
809
  let version = versions[i].join("-")
807
- if (!after_versions[version]) {
808
- new_doc.mergeBytes(
809
- OpLog_create_bytes(
810
- version,
811
- parentss[i].map((x) => x.join("-")),
812
- content ? start + (i - base_i) : start,
813
- content?.[0]
814
- )
810
+ if (!after_versions[version]) new_doc.mergeBytes(
811
+ dt_create_bytes(
812
+ version,
813
+ parentss[i].map((x) => x.join("-")),
814
+ op_run.fwd ?
815
+ (op_run.content ?
816
+ op_run.start + (i - base_i) :
817
+ op_run.start) :
818
+ op_run.end - 1 - (i - base_i),
819
+ op_run.content?.[i - base_i]
815
820
  )
816
- }
817
- if (op_run.content) content = content.slice(1)
821
+ )
818
822
  }
819
- content = ""
820
823
  }
821
- if (op_run.content) content += op_run.content[j]
822
824
  }
823
825
  })
824
826
  return new_doc
825
827
  }
826
828
 
827
- function defrag_dt(doc) {
828
- let fresh_doc = new Doc("server")
829
- fresh_doc.mergeBytes(doc.toBytes())
830
- return fresh_doc
831
- }
829
+ function dt_get_patches(doc, version = null) {
830
+ let bytes = doc.toBytes()
831
+ doc = Doc.fromBytes(bytes)
832
+
833
+ let [_agents, versions, parentss] = dt_parse([...bytes])
832
834
 
833
- function OpLog_get_patches(bytes, op_runs) {
834
- // console.log(`op_runs = `, op_runs);
835
+ let op_runs = []
836
+ if (version) {
837
+ let frontier = {}
838
+ version.forEach((x) => frontier[x] = true)
839
+ let local_version = []
840
+ for (let i = 0; i < versions.length; i++)
841
+ if (frontier[versions[i].join("-")]) local_version.push(i)
842
+ let after_bytes = doc.getPatchSince(new Uint32Array(local_version))
835
843
 
836
- let [agents, versions, parentss] = parseDT([...bytes])
844
+ ;[_agents, versions, parentss] = dt_parse([...after_bytes])
837
845
 
838
- // console.log(JSON.stringify({agents, versions, parentss}, null, 4))
846
+ let before_doc = dt_get(doc, version)
847
+ let before_doc_frontier = before_doc.getLocalVersion()
848
+
849
+ before_doc.mergeBytes(after_bytes)
850
+ op_runs = before_doc.getOpsSince(before_doc_frontier)
851
+ } else op_runs = doc.getOpsSince([])
839
852
 
840
853
  let i = 0
841
854
  let patches = []
@@ -845,11 +858,11 @@ function OpLog_get_patches(bytes, op_runs) {
845
858
  let start = op_run.start
846
859
  let end = start + 1
847
860
  if (op_run.content) op_run.content = [...op_run.content]
848
- let content = op_run.content?.[0]
849
861
  let len = op_run.end - op_run.start
850
862
  for (let j = 1; j <= len; j++) {
851
863
  let I = i + j
852
864
  if (
865
+ (!op_run.content && op_run.fwd) ||
853
866
  j == len ||
854
867
  parentss[I].length != 1 ||
855
868
  parentss[I][0][0] != versions[I - 1][0] ||
@@ -857,30 +870,38 @@ function OpLog_get_patches(bytes, op_runs) {
857
870
  versions[I][0] != versions[I - 1][0] ||
858
871
  versions[I][1] != versions[I - 1][1] + 1
859
872
  ) {
873
+ let s = op_run.fwd ?
874
+ (op_run.content ?
875
+ start :
876
+ op_run.start) :
877
+ (op_run.start + (op_run.end - end))
878
+ let e = op_run.fwd ?
879
+ (op_run.content ?
880
+ end :
881
+ op_run.start + (end - start)) :
882
+ (op_run.end - (start - op_run.start))
860
883
  patches.push({
861
884
  version,
862
885
  parents,
863
886
  unit: "text",
864
- range: content ? `[${start}:${start}]` : `[${start}:${end}]`,
865
- content: content ?? "",
866
- start,
867
- end,
887
+ range: op_run.content ? `[${s}:${s}]` : `[${s}:${e}]`,
888
+ content: op_run.content?.slice(start - op_run.start, end - op_run.start).join("") ?? "",
889
+ start: s,
890
+ end: e,
868
891
  })
869
892
  if (j == len) break
870
893
  version = versions[I].join("-")
871
894
  parents = parentss[I].map((x) => x.join("-")).sort()
872
895
  start = op_run.start + j
873
- content = ""
874
896
  }
875
897
  end++
876
- if (op_run.content) content += op_run.content[j]
877
898
  }
878
899
  i += len
879
900
  })
880
901
  return patches
881
902
  }
882
903
 
883
- function parseDT(byte_array) {
904
+ function dt_parse(byte_array) {
884
905
  if (new TextDecoder().decode(new Uint8Array(byte_array.splice(0, 8))) !== "DMNDTYPS") throw new Error("dt parse error, expected DMNDTYPS")
885
906
 
886
907
  if (byte_array.shift() != 0) throw new Error("dt parse error, expected version 0")
@@ -975,8 +996,7 @@ function parseDT(byte_array) {
975
996
  return [agents, versions, parentss]
976
997
  }
977
998
 
978
- function OpLog_create_bytes(version, parents, pos, ins) {
979
- // console.log(`args = ${JSON.stringify({ version, parents, pos, ins }, null, 4)}`)
999
+ function dt_create_bytes(version, parents, pos, ins) {
980
1000
 
981
1001
  function write_varint(bytes, value) {
982
1002
  while (value >= 0x80) {
@@ -1122,6 +1142,10 @@ function OpLog_create_bytes(version, parents, pos, ins) {
1122
1142
  return bytes
1123
1143
  }
1124
1144
 
1145
+ function defrag_dt(doc) {
1146
+ return Doc.fromBytes(doc.toBytes(), 'server')
1147
+ }
1148
+
1125
1149
  function OpLog_remote_to_local(doc, frontier) {
1126
1150
  let map = Object.fromEntries(frontier.map((x) => [x, true]))
1127
1151
 
@@ -1496,7 +1520,46 @@ function validate_patch(x) {
1496
1520
  if (typeof x.content !== 'string') throw new Error(`invalid patch content: must be a string`)
1497
1521
  }
1498
1522
 
1523
+ function createSimpleCache(size) {
1524
+ const maxSize = size
1525
+ const cache = new Map()
1526
+
1527
+ return {
1528
+ put(key, value) {
1529
+ if (cache.has(key)) {
1530
+ // If the key already exists, update its value and move it to the end
1531
+ cache.delete(key)
1532
+ cache.set(key, value)
1533
+ } else {
1534
+ // If the cache is full, remove the oldest entry
1535
+ if (cache.size >= maxSize) {
1536
+ const oldestKey = cache.keys().next().value
1537
+ cache.delete(oldestKey)
1538
+ }
1539
+ // Add the new key-value pair
1540
+ cache.set(key, value)
1541
+ }
1542
+ },
1543
+
1544
+ get(key) {
1545
+ if (!cache.has(key)) {
1546
+ return null
1547
+ }
1548
+ // Move the accessed item to the end (most recently used)
1549
+ const value = cache.get(key)
1550
+ cache.delete(key)
1551
+ cache.set(key, value)
1552
+ return value
1553
+ },
1554
+ }
1555
+ }
1556
+
1499
1557
  braid_text.encode_filename = encode_filename
1500
1558
  braid_text.decode_filename = decode_filename
1501
1559
 
1560
+ braid_text.dt_get = dt_get
1561
+ braid_text.dt_get_patches = dt_get_patches
1562
+ braid_text.dt_parse = dt_parse
1563
+ braid_text.dt_create_bytes = dt_create_bytes
1564
+
1502
1565
  module.exports = braid_text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "Library for collaborative text over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braidjs",
package/server-demo.js CHANGED
@@ -64,7 +64,7 @@ var server = require("http").createServer(async (req, res) => {
64
64
  // }
65
65
 
66
66
  // Create some initial text for new documents
67
- if (await braid_text.get(req.url) === undefined) {
67
+ if (!(await braid_text.get(req.url, {})).version.length) {
68
68
  await braid_text.put(req.url, {body: 'This is a fresh blank document, ready for you to edit.' })
69
69
  }
70
70
 
package/test.js ADDED
@@ -0,0 +1,476 @@
1
+
2
+ let { Doc } = require("diamond-types-node")
3
+ let {dt_get, dt_get_patches, dt_parse, dt_create_bytes} = require('./index.js')
4
+
5
+ async function main() {
6
+ let best_seed = 0
7
+ let best_n = Infinity
8
+ let base = Math.floor(Math.random() * 10000000)
9
+
10
+ let og_log = console.log
11
+ console.log = () => {}
12
+ for (let t = 0; t < 10000; t++) {
13
+ let seed = base + t
14
+ // for (let t = 0; t < 10; t++) {
15
+ // let seed = 1188661 + t
16
+
17
+ og_log(`t = ${t}, seed = ${seed}, best_n = ${best_n} @ ${best_seed}`)
18
+ Math.randomSeed(seed)
19
+
20
+ let n = Math.floor(Math.random() * 15)
21
+ console.log(`n = ${n}`)
22
+
23
+ try {
24
+ // 1. create a bunch of edits to a dt
25
+ let doc = new Doc('server')
26
+
27
+ let middle_doc = null
28
+
29
+ if (!middle_doc && (Math.random() < 1/n || n == 0)) {
30
+ middle_doc = Doc.fromBytes(doc.toBytes())
31
+ }
32
+ for (let i = 0; i < n; i++) {
33
+ make_random_edit(doc)
34
+
35
+ if (!middle_doc && (Math.random() < 1/n || i == n - 1)) {
36
+ middle_doc = Doc.fromBytes(doc.toBytes())
37
+ }
38
+ }
39
+ if (!middle_doc) throw 'bad'
40
+
41
+ // 2. let x = the resulting string
42
+ let x = doc.get()
43
+ console.log('x = ' + x)
44
+
45
+ // // 3. use the code for sending these edits over the wire to create a new dt
46
+ let updates = dt_get_patches(doc)
47
+ console.log(updates)
48
+
49
+ let new_doc = new Doc('server')
50
+ apply_updates(new_doc, updates)
51
+ let y = new_doc.get()
52
+ console.log('y = ' + y)
53
+
54
+ // 4. is the resulting string == x?
55
+ console.log(x == y)
56
+ if (x != y && n < best_n) {
57
+ best_n = n
58
+ best_seed = seed
59
+ }
60
+
61
+ // 5. test dt_get
62
+ let middle_v = middle_doc.getRemoteVersion().map(x => x.join('-'))
63
+ let new_middle_doc = dt_get(doc, middle_v)
64
+ console.log('new_middle_doc = ' + new_middle_doc.get())
65
+ if (middle_doc.get() != new_middle_doc.get() && n < best_n) {
66
+ best_n = n
67
+ best_seed = seed
68
+ }
69
+
70
+ // 6. test dt_get_patches(doc, version)
71
+ if (true) {
72
+ let updates = dt_get_patches(doc, middle_v)
73
+ console.log(`updates:`, updates)
74
+
75
+ apply_updates(middle_doc, updates)
76
+ console.log(`middle_doc2:${middle_doc.get()}`)
77
+ if (middle_doc.get() != doc.get() && n < best_n) {
78
+ best_n = n
79
+ best_seed = seed
80
+ }
81
+ }
82
+
83
+ // 7. try applying a patch that's out of range..
84
+ // if (true) {
85
+ // let agent = Math.random().toString(36).slice(2)
86
+ // let parents = doc.getRemoteVersion().map(x => x.join('-'))
87
+ // let len = doc.len()
88
+ // let args = [`${agent}-0`, parents, len + 1, 'c']
89
+ // console.log('ARGS:', args)
90
+ // try {
91
+ // doc.mergeBytes(dt_create_bytes(...args))
92
+ // } catch (e) {
93
+ // console.log(`EEEE = ${e}`)
94
+ // }
95
+ // console.log('did that..')
96
+ // }
97
+ } catch (e) {
98
+ if (console.log == og_log) throw e
99
+ if (n < best_n) {
100
+ best_n = n
101
+ best_seed = seed
102
+ }
103
+ }
104
+ }
105
+ og_log(`best_seed = ${best_seed}, best_n = ${best_n}`)
106
+ }
107
+
108
+ function make_random_edit(doc) {
109
+ let [agents, versions, _parentss] = dt_parse([...doc.toBytes()])
110
+
111
+ let agent = (agents.length && Math.random() > 0.5) ?
112
+ agents[Math.floor(Math.random() * agents.length)] :
113
+ Math.random().toString(36).slice(2)
114
+
115
+ let include_versions = []
116
+ let base_seq = -1
117
+ for (let i = 0; i < versions.length; i++) {
118
+ let [a, seq] = versions[i]
119
+ if (a == agent || Math.random() > 0.5) {
120
+ include_versions.push(a + '-' + seq)
121
+ if (a == agent && seq > base_seq) base_seq = seq
122
+ }
123
+ }
124
+ base_seq++
125
+
126
+ console.log({agents, versions, include_versions})
127
+
128
+ let parent_doc = dt_get(doc, include_versions)
129
+
130
+ let parents = parent_doc.getRemoteVersion().map(x => x.join('-'))
131
+
132
+ let len = parent_doc.len()
133
+ console.log(`len = ${len}`)
134
+
135
+ if (len && Math.random() > 0.5) {
136
+ // delete
137
+ let start = Math.floor(Math.random() * len)
138
+ let del_len = Math.floor(Math.random() * (len - start - 1)) + 1
139
+
140
+ for (let i = 0; i < del_len; i++) {
141
+ let v = `${agent}-${base_seq++}`
142
+ let args = [v, parents, start + del_len - 1 - i, null]
143
+ console.log(args)
144
+ doc.mergeBytes(dt_create_bytes(...args))
145
+ parents = [v]
146
+ }
147
+ } else {
148
+ // insert
149
+ let start = Math.floor(Math.random() * (len + 1))
150
+ let ins_len = Math.floor(Math.random() * 10) + 1
151
+
152
+ for (let i = 0; i < ins_len; i++) {
153
+ let v = `${agent}-${base_seq++}`
154
+ let args = [v, parents, start++, getRandomCharacter()]
155
+ console.log(args)
156
+ doc.mergeBytes(dt_create_bytes(...args))
157
+ parents = [v]
158
+ }
159
+ }
160
+
161
+ // work here
162
+ console.log(`doc => ${doc.get()}`)
163
+ }
164
+
165
+ function apply_updates(doc, updates) {
166
+ for (let u of updates) {
167
+ u.range = u.range.match(/\d+/g).map((x) => parseInt(x))
168
+ u.content = [...u.content]
169
+
170
+ let v = u.version
171
+ let ps = u.parents
172
+
173
+ console.log('UPDATE:', u)
174
+
175
+ // delete
176
+ for (let i = u.range[1] - 1; i >= u.range[0]; i--) {
177
+
178
+ // work here
179
+ let args = [v, ps, i, null]
180
+ console.log(`args`, args)
181
+
182
+ doc.mergeBytes(dt_create_bytes(...args))
183
+ ps = [v]
184
+ v = decode_version(v)
185
+ v = `${v[0]}-${v[1] + 1}`
186
+ }
187
+ // insert
188
+ for (let i = 0; i < u.content?.length ?? 0; i++) {
189
+ let c = u.content[i]
190
+
191
+ // work here
192
+ let args = [v, ps, u.range[0] + i, c]
193
+ console.log(`args`, args)
194
+
195
+ doc.mergeBytes(dt_create_bytes(...args))
196
+ ps = [v]
197
+ v = decode_version(v)
198
+ v = `${v[0]}-${v[1] + 1}`
199
+ }
200
+ }
201
+ }
202
+
203
+ //////////////////////////////////////////////////////////////////
204
+ //////////////////////////////////////////////////////////////////
205
+ //////////////////////////////////////////////////////////////////
206
+
207
+ function getRandomCharacter() {
208
+ const characters = [
209
+ // ASCII characters
210
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
211
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
212
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
213
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
214
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
215
+ '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '+', '=',
216
+
217
+ // Unicode characters (each is a single code point)
218
+ 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', // Greek letters
219
+ '零', '一', '二', '三', '四', '五', '六', '七', // Chinese numerals
220
+ '☀', '☁', '☂', '☃', '☄', '★', '☆', '☇', // Miscellaneous symbols
221
+ '🌈', '🌞', '🌝', '🌚', '🌕', '🌖', '🌗', '🌘', // Emoji (may be multiple UTF-16 units)
222
+ 'Ω', 'π', '∑', '√', '∫', '∀', '∂', '∃', // Mathematical symbols
223
+ 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', // Japanese Hiragana
224
+ 'ا', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ', 'د', // Arabic letters
225
+ ];
226
+
227
+ const randomIndex = Math.floor(Math.random() * characters.length);
228
+ return characters[randomIndex];
229
+ }
230
+
231
+ function decode_version(v) {
232
+ let m = v.match(/^(.*)-(\d+)$/s)
233
+ if (!m) throw new Error(`invalid actor-seq version: ${v}`)
234
+ return [m[1], parseInt(m[2])]
235
+ }
236
+
237
+ /////////
238
+
239
+ // the next two functions added by me
240
+
241
+ function create_rand(seed) {
242
+ if (typeof (seed) == 'string') {
243
+ var t = new MersenneTwister(0)
244
+ var a = []
245
+ for (var i = 0; i < seed.length; i++)
246
+ a[i] = seed.charCodeAt(i)
247
+ t.init_by_array(a, a.length)
248
+ } else if (typeof (seed) == 'number') {
249
+ var t = new MersenneTwister(seed)
250
+ } else {
251
+ var t = new MersenneTwister()
252
+ }
253
+ return () => t.random()
254
+ }
255
+
256
+ Math.randomSeed = function (seed) {
257
+ var r = create_rand(seed)
258
+ Math.random = () => r()
259
+ }
260
+
261
+ /* The following piece of code is an implementation of MersenneTwister object
262
+ taken from https://gist.github.com/banksean/300494, with one method
263
+ xor_array(array, size) added.
264
+ */
265
+
266
+ /*
267
+ I've wrapped Makoto Matsumoto and Takuji Nishimura's code in a namespace
268
+ so it's better encapsulated. Now you can have multiple random number generators
269
+ and they won't stomp all over eachother's state.
270
+
271
+ If you want to use this as a substitute for Math.random(), use the random()
272
+ method like so:
273
+
274
+ var m = new MersenneTwister();
275
+ var randomNumber = m.random();
276
+
277
+ You can also call the other genrand_{foo}() methods on the instance.
278
+
279
+ If you want to use a specific seed in order to get a repeatable random
280
+ sequence, pass an integer into the constructor:
281
+
282
+ var m = new MersenneTwister(123);
283
+
284
+ and that will always produce the same random sequence.
285
+
286
+ Sean McCullough (banksean@gmail.com)
287
+ */
288
+
289
+ /*
290
+ A C-program for MT19937, with initialization improved 2002/1/26.
291
+ Coded by Takuji Nishimura and Makoto Matsumoto.
292
+
293
+ Before using, initialize the state by using init_genrand(seed)
294
+ or init_by_array(init_key, key_length).
295
+
296
+ Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
297
+ All rights reserved.
298
+
299
+ Redistribution and use in source and binary forms, with or without
300
+ modification, are permitted provided that the following conditions
301
+ are met:
302
+
303
+ 1. Redistributions of source code must retain the above copyright
304
+ notice, this list of conditions and the following disclaimer.
305
+
306
+ 2. Redistributions in binary form must reproduce the above copyright
307
+ notice, this list of conditions and the following disclaimer in the
308
+ documentation and/or other materials provided with the distribution.
309
+
310
+ 3. The names of its contributors may not be used to endorse or promote
311
+ products derived from this software without specific prior written
312
+ permission.
313
+
314
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
315
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
316
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
317
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
318
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
319
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
320
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
321
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
322
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
323
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
324
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
325
+
326
+
327
+ Any feedback is very welcome.
328
+ http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
329
+ email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
330
+ */
331
+
332
+ var MersenneTwister = function (seed) {
333
+ if (seed == undefined) {
334
+ seed = new Date().getTime();
335
+ }
336
+ /* Period parameters */
337
+ this.N = 624;
338
+ this.M = 397;
339
+ this.MATRIX_A = 0x9908b0df; /* constant vector a */
340
+ this.UPPER_MASK = 0x80000000; /* most significant w-r bits */
341
+ this.LOWER_MASK = 0x7fffffff; /* least significant r bits */
342
+
343
+ this.mt = new Array(this.N); /* the array for the state vector */
344
+ this.mti = this.N + 1; /* mti==N+1 means mt[N] is not initialized */
345
+
346
+ this.init_genrand(seed);
347
+ }
348
+
349
+ /* initializes mt[N] with a seed */
350
+ MersenneTwister.prototype.init_genrand = function (s) {
351
+ this.mt[0] = s >>> 0;
352
+ for (this.mti = 1; this.mti < this.N; this.mti++) {
353
+ var s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
354
+ this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
355
+ + this.mti;
356
+ /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
357
+ /* In the previous versions, MSBs of the seed affect */
358
+ /* only MSBs of the array mt[]. */
359
+ /* 2002/01/09 modified by Makoto Matsumoto */
360
+ this.mt[this.mti] >>>= 0;
361
+ /* for >32 bit machines */
362
+ }
363
+ }
364
+
365
+ /* initialize by an array with array-length */
366
+ /* init_key is the array for initializing keys */
367
+ /* key_length is its length */
368
+ /* slight change for C++, 2004/2/26 */
369
+ MersenneTwister.prototype.init_by_array = function (init_key, key_length) {
370
+ var i, j, k;
371
+ this.init_genrand(19650218);
372
+ i = 1; j = 0;
373
+ k = (this.N > key_length ? this.N : key_length);
374
+ for (; k; k--) {
375
+ var s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30)
376
+ this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525)))
377
+ + init_key[j] + j; /* non linear */
378
+ this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
379
+ i++; j++;
380
+ if (i >= this.N) { this.mt[0] = this.mt[this.N - 1]; i = 1; }
381
+ if (j >= key_length) j = 0;
382
+ }
383
+ for (k = this.N - 1; k; k--) {
384
+ var s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30);
385
+ this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941))
386
+ - i; /* non linear */
387
+ this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
388
+ i++;
389
+ if (i >= this.N) { this.mt[0] = this.mt[this.N - 1]; i = 1; }
390
+ }
391
+
392
+ this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
393
+ }
394
+
395
+ /* XORs the mt array with a given array xor_key of length key_length */
396
+ MersenneTwister.prototype.xor_array = function (xor_key, key_length) {
397
+ var i, j;
398
+ j = 0;
399
+ for (i = 0; i < this.N; i++) {
400
+ this.mt[i] ^= xor_key[j];
401
+ this.mt[i] >>>= 0;
402
+ j++;
403
+ if (j >= key_length) j = 0;
404
+ }
405
+ }
406
+
407
+ /* generates a random number on [0,0xffffffff]-interval */
408
+ MersenneTwister.prototype.genrand_int32 = function () {
409
+ var y;
410
+ var mag01 = new Array(0x0, this.MATRIX_A);
411
+ /* mag01[x] = x * MATRIX_A for x=0,1 */
412
+
413
+ if (this.mti >= this.N) { /* generate N words at one time */
414
+ var kk;
415
+
416
+ if (this.mti == this.N + 1) /* if init_genrand() has not been called, */
417
+ this.init_genrand(5489); /* a default initial seed is used */
418
+
419
+ for (kk = 0; kk < this.N - this.M; kk++) {
420
+ y = (this.mt[kk] & this.UPPER_MASK) | (this.mt[kk + 1] & this.LOWER_MASK);
421
+ this.mt[kk] = this.mt[kk + this.M] ^ (y >>> 1) ^ mag01[y & 0x1];
422
+ }
423
+ for (; kk < this.N - 1; kk++) {
424
+ y = (this.mt[kk] & this.UPPER_MASK) | (this.mt[kk + 1] & this.LOWER_MASK);
425
+ this.mt[kk] = this.mt[kk + (this.M - this.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
426
+ }
427
+ y = (this.mt[this.N - 1] & this.UPPER_MASK) | (this.mt[0] & this.LOWER_MASK);
428
+ this.mt[this.N - 1] = this.mt[this.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1];
429
+
430
+ this.mti = 0;
431
+ }
432
+
433
+ y = this.mt[this.mti++];
434
+
435
+ /* Tempering */
436
+ y ^= (y >>> 11);
437
+ y ^= (y << 7) & 0x9d2c5680;
438
+ y ^= (y << 15) & 0xefc60000;
439
+ y ^= (y >>> 18);
440
+
441
+ return y >>> 0;
442
+ }
443
+
444
+ /* generates a random number on [0,0x7fffffff]-interval */
445
+ MersenneTwister.prototype.genrand_int31 = function () {
446
+ return (this.genrand_int32() >>> 1);
447
+ }
448
+
449
+ /* generates a random number on [0,1]-real-interval */
450
+ MersenneTwister.prototype.genrand_real1 = function () {
451
+ return this.genrand_int32() * (1.0 / 4294967295.0);
452
+ /* divided by 2^32-1 */
453
+ }
454
+
455
+ /* generates a random number on [0,1)-real-interval */
456
+ MersenneTwister.prototype.random = function () {
457
+ return this.genrand_int32() * (1.0 / 4294967296.0);
458
+ /* divided by 2^32 */
459
+ }
460
+
461
+ /* generates a random number on (0,1)-real-interval */
462
+ MersenneTwister.prototype.genrand_real3 = function () {
463
+ return (this.genrand_int32() + 0.5) * (1.0 / 4294967296.0);
464
+ /* divided by 2^32 */
465
+ }
466
+
467
+ /* generates a random number on [0,1) with 53-bit resolution*/
468
+ MersenneTwister.prototype.genrand_res53 = function () {
469
+ var a = this.genrand_int32() >>> 5, b = this.genrand_int32() >>> 6;
470
+ return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
471
+ }
472
+
473
+ /* These real versions are due to Isaku Wada, 2002/01/09 added */
474
+
475
+ ////////////////////////////////////////////////////////////////////
476
+ main()