querysub 0.433.0 → 0.436.0

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 (74) hide show
  1. package/.eslintrc.js +50 -50
  2. package/bin/deploy.js +0 -0
  3. package/bin/function.js +0 -0
  4. package/bin/server.js +0 -0
  5. package/costsBenefits.txt +115 -115
  6. package/deploy.ts +2 -2
  7. package/package.json +1 -1
  8. package/spec.txt +1192 -1192
  9. package/src/-a-archives/archives.ts +202 -202
  10. package/src/-a-archives/archivesBackBlaze.ts +1 -0
  11. package/src/-a-archives/archivesDisk.ts +454 -454
  12. package/src/-a-auth/certs.ts +540 -540
  13. package/src/-a-auth/node-forge-ed25519.d.ts +16 -16
  14. package/src/-b-authorities/dnsAuthority.ts +138 -138
  15. package/src/-c-identity/IdentityController.ts +258 -258
  16. package/src/-d-trust/NetworkTrust2.ts +180 -180
  17. package/src/-e-certs/EdgeCertController.ts +252 -252
  18. package/src/-e-certs/certAuthority.ts +201 -201
  19. package/src/-f-node-discovery/NodeDiscovery.ts +640 -640
  20. package/src/-g-core-values/NodeCapabilities.ts +200 -200
  21. package/src/-h-path-value-serialize/stringSerializer.ts +175 -175
  22. package/src/0-path-value-core/PathValueCommitter.ts +468 -468
  23. package/src/0-path-value-core/pathValueCore.ts +2 -2
  24. package/src/2-proxy/PathValueProxyWatcher.ts +2542 -2542
  25. package/src/2-proxy/TransactionDelayer.ts +94 -94
  26. package/src/2-proxy/pathDatabaseProxyBase.ts +36 -36
  27. package/src/2-proxy/pathValueProxy.ts +159 -159
  28. package/src/3-path-functions/PathFunctionRunnerMain.ts +87 -87
  29. package/src/3-path-functions/pathFunctionLoader.ts +516 -516
  30. package/src/3-path-functions/tests/rejectTest.ts +76 -76
  31. package/src/4-deploy/deployCheck.ts +6 -6
  32. package/src/4-dom/css.tsx +29 -29
  33. package/src/4-dom/cssTypes.d.ts +211 -211
  34. package/src/4-dom/qreact.tsx +2799 -2799
  35. package/src/4-dom/qreactTest.tsx +410 -410
  36. package/src/4-querysub/permissions.ts +335 -335
  37. package/src/4-querysub/querysubPrediction.ts +483 -483
  38. package/src/5-diagnostics/qreactDebug.tsx +346 -346
  39. package/src/TestController.ts +34 -34
  40. package/src/bits.ts +104 -104
  41. package/src/buffers.ts +69 -69
  42. package/src/diagnostics/ActionsHistory.ts +57 -57
  43. package/src/diagnostics/listenOnDebugger.ts +71 -71
  44. package/src/diagnostics/periodic.ts +111 -111
  45. package/src/diagnostics/trackResources.ts +91 -91
  46. package/src/diagnostics/watchdog.ts +120 -120
  47. package/src/errors.ts +133 -133
  48. package/src/forceProduction.ts +2 -2
  49. package/src/fs.ts +80 -80
  50. package/src/functional/diff.ts +857 -857
  51. package/src/functional/promiseCache.ts +78 -78
  52. package/src/functional/random.ts +8 -8
  53. package/src/functional/stats.ts +60 -60
  54. package/src/heapDumps.ts +665 -665
  55. package/src/https.ts +1 -1
  56. package/src/library-components/AspectSizedComponent.tsx +87 -87
  57. package/src/library-components/ButtonSelector.tsx +64 -64
  58. package/src/library-components/DropdownCustom.tsx +150 -150
  59. package/src/library-components/DropdownSelector.tsx +31 -31
  60. package/src/library-components/InlinePopup.tsx +66 -66
  61. package/src/misc/color.ts +29 -29
  62. package/src/misc/hash.ts +83 -83
  63. package/src/misc/ipPong.js +13 -13
  64. package/src/misc/networking.ts +1 -1
  65. package/src/misc/random.ts +44 -44
  66. package/src/misc.ts +196 -196
  67. package/src/path.ts +255 -255
  68. package/src/persistentLocalStore.ts +41 -41
  69. package/src/promise.ts +14 -14
  70. package/src/storage/fileSystemPointer.ts +71 -71
  71. package/src/test/heapProcess.ts +35 -35
  72. package/src/zip.ts +15 -15
  73. package/tsconfig.json +26 -26
  74. package/yarnSpec.txt +56 -56
package/spec.txt CHANGED
@@ -1,1193 +1,1193 @@
1
-
2
-
3
- Trigger values
4
- 1) validStateComputer.ingestValuesAndValidStates
5
- 1.5) authorityStorage.ingestValues
6
- 2) authorityStorage.ingestValues
7
- 3) lockWatcher2.watchValueLocks
8
- 4) validStateComputer.computeValidStates
9
- 5) lockWatcher2.getValuePathWatchers
10
- 6) pathWatcher.triggerValuesChanged
11
- 7) pathWatcher.triggerLatestWatcher
12
- 8) PathValueControllerBase/clientWatcher.localOnValueCallback
13
-
14
- Source of values
15
- 1) clientWatcher.setValues
16
- 2) PathValueCommitter.commitValues
17
- 3) (predictions) validStateComputer.ingestValuesAndValidStates
18
- 4) (non-predictions) PathValueControllerBase.createValues
19
- 5) validStateComputer.ingestValuesAndValidStates
20
-
21
- Archiving values
22
- 1) authorityStorage.ingestValues
23
- 2) pathValueArchives.archiveValues
24
-
25
- TODO: pathValueAuthority detection
26
-
27
-
28
- Core parts
29
- - Valid evaluation
30
- - Needs external value syncing, which can give it all past values, and all future values for a path
31
- - Value syncer
32
- - Needs someone to give it authorities, and tell it when an authority goes down (at which point it finds a new authority and syncs the new values)
33
- - Authority tracker (supports registering, checking, notifying, and generally synchronizing all authority information between all authorities)
34
- - No requirements
35
- - Authority setuper
36
- - Needs authority tracker
37
- - Needs value syncer
38
- - Authority (stitches together functionality)
39
- - Needs authority tracker
40
- - Needs authority setuper
41
- - Needs value syncer
42
- - Needs valid evaluation
43
- - Disk value saving/retrieval
44
-
45
- FunctionRunner is funny. It wants a full history, for reading, but does not calculate the valid states of PathValues.
46
-
47
-
48
- Optimization / optional features list
49
- Valid Evaluation
50
- - Only need to sync some data, not all data.
51
- - event: sometimes we discard data
52
- - isTransparent: Sometimes, even though two values are different, they can be considered equivalent.
53
- - gc: Older values won't change their valid state, so we can discard their locks.
54
- Value Syncer
55
- - Path watches
56
- - Parent path watches
57
- - Authority path (tree) watches
58
- - Valid watching (authorities know this, so it's free to also sync this)
59
- - Only latest/valid state watches (no history, just latest, more efficient)
60
- - Special connection clobber logic (you can't store the history after you disconnect if you are not the authority)
61
- - Special logic
62
- - Reused for internal watches (rendering, etc)
63
- Authority tracker
64
- - Immutable authorities, to simplify usages
65
- - Path requests => authorities might take slices from multiple authorities
66
- Authority setuper
67
- - Once on startup, so we don't change authorities, to simplify implementation
68
- - Leverage disk snapshots to make loading faster and lighter for existing nodes
69
- Authority
70
- - The stitching together of all the values is reused by the function runner in order to rerun functions when they are invalidated
71
- Disk value saving/retrieval
72
- - Only old values with discarded locks are written, and the history is compressed occasionally
73
-
74
-
75
-
76
-
77
-
78
- *** Goal is to get to === Full public website stack milestone === ***
79
- === 3 tasks to fully fledged syncing KVP database, 2023 / 25 / 2 ===
80
- === 1 tasks to fully fledged syncing KVP database, 2023 / 26 / 2 ===
81
- === FINISHED fully fledged syncing KVP database, 2023 / 28 / 1 ===
82
- === 6 tasks to "Full public website stack", 2023 / 31 / 1 ===
83
- === 5 tasks to "Full public website stack", 2023 / 31 / 1 ===
84
- === FunctionRunner bones done!, 2023 / 31 / 2 ===
85
- === Disk garbage collection bones done, 2023 / 11 / 3 ===
86
- === Access for non-network nodes (via permissions) done, 2023 / 19 / 3 ===
87
- === Clientside + clientside function prediction done! 2023 / 23 / 3 ===
88
- === Prototype SD app with 2 creation modes, arbitrary model support, paint, diffuse, openpose, onnx model generation, etc. 2023 / 10 / 1 ===
89
- === Lots of failed embedding training research. 2023 / 12 / 18 ===
90
- === Better single source of truth. 2023 / 12 / 31 ===
91
- === Better serialization format. 2023 / 12 / 31 ===
92
- === Fixed storage / multi process storage. 2023 / 12 / 31 ===
93
-
94
- TIMING: Transactions, currently about ~1.5ms per transaction + 10us per write and read path in the transaction
95
- - About 5ms latency, the fast times are only if you don't wait for transactions to finish (because why would you wait when the whole system is based on predicting writes?)
96
- - With 1 watcher (with 0 watchers transactions are probably about 0.5ms per)
97
- - Much of the transaction over is serialization time, mostly in JSON.stringify (to send the value to the watcher)
98
- - MUST FASTER NOW, if they are batched
99
-
100
- TIMING: Function calls, about ~1ms-3ms for a trivial function, if it can be batched with similar functions
101
- - Slower if we need to sync new paths
102
-
103
-
104
- Fixed function props / function closure parsing
105
- - Presently, if a parent re-renders, and it passed a lambda to a component, that component will always have changed props. HOWEVER, this is very inefficient!
106
- 1) Process the file to convert lambdas to provide debug information which includes all of the values they close upon (as properties set on the lambda itself). Also an id, unique to that specific piece of code.
107
- 2) When evaluating if props changed, if the lambda id is the same, and the closed upone variables are ===, then... it's the same thing, so consider the prop unchanged
108
- 3) Test if all of our "updateOperation" lambdas in SideOpConfig.tsx work with this. I THINK updateOperation is the same, but if it isn't... we can always recognize nested lambdas.
109
-
110
- Option to not use permissions checks locks
111
- 1) Create a way to have any code run without locks (we might already have this?)
112
- 2) Add a flag in schemas to either specify all functions, or certain functions should run permissions without locks.
113
- - Also something to specify a function SHOULD run permissions with locks
114
- - Using the ArchiveViewer verify "heartbeat" goes from 25 to 0 locks
115
- - If you step into pathValueClientWatcher.ts:setValues in FunctionRunner, you can see the actual paths that are used.
116
- - Benchmark a simple function to see how much faster this is, and how much less memory we use
117
- - If it doesn't matter... maybe remove the feature?
118
-
119
- More corruption resistance files
120
- - Add a new serialization format, via versioning in the settings
121
- - Each datum will be length prefixed WITH a special sentinel AND with a checksum for the datum data
122
- - The sentinel can depend on the length (but otherwise be constant)
123
- - We can have a recovery mode (automatically attempted if the checksums are wrong?) where we scan the file for length+sentinel pairs (where the sentinel matches the checksum), see which ones have a valid checksum, and then use an algorithm to decide which ones to use
124
- - Maybe we sort by success rate, which is [-numberOfValidChecksumsOverlapped, length], which is very likely to work EVEN if there are values which have the right checksum, but are actuall corrupted.
125
- - As long as the checksums are random enough, so... we should never use any small values for checksums, etc.
126
- - Record the value layout in the settings, so we can generate arbitrary values, even if we've never seen that layout before.
127
- - A series of parallel object arrays, with each object having flags (which indicate which values exist), values which always exist, etc
128
- - Value types will be string, float64, byte, Buffer[]
129
- - and... we might as well add support for short, int, float, and uint (uint is a good way to store a guid, via storing 8 uint variables).
130
-
131
- Schema/binary PathValues accesses
132
- PER Path, not per value. Values will likely be lazily deserialized anyways, making them either pointers, or just numbers, stored in unchanging objects, which... should be quite fast.
133
- 0) First our stream/schema wrappers. Which at first just convert to raw path reads/writes, but once we add schemas they can efficiently create/use those
134
- 1) PathValue.path (string[]), need to be converted to an opaque type `Path`
135
- - Helpers to manipulate it, and go back to string[], string, etc.
136
- 2) Have Path be able to store itself in schema + variables mode
137
- - Where schema is shared across all Path instances
138
- { schema: { path: (string|Variable)[] }; variables: (string | number)[]; }
139
- - Add functions to access the schema and variable values
140
- 3) Create an efficient `Map<Path, T>`, via using schemas from the paths
141
- - By requiring Path as the input we can directly use the schema to narrow down the cases, and then within that we just have to lookup values by variables
142
- 4) Use Path everywhere, replacing PathValue.path, using our efficient lookup to manage it.
143
- - I think a lookup should handle all the cases. We should be able to nest them as well?
144
- 5) This SHOULD let us entirely get rid of path joining, which should be SO much faster.
145
- 6) Update serialization to keep schemas, instead of converting them back to paths. This should be more efficient, and a lot smaller.
146
- - Values serialization won't change, and we still need to encode the variables, but... it should still be a lot faster.
147
- 7) Try to remove as many Path conversions to string (and also to a lesser degree string[]), as possible, by making them schema aware.
148
- 8) Investigate further optimizations
149
- - Replacing variables with numbers, so we our internals Maps can be replaced with arrays / typed arrays
150
-
151
- IMPORTANT! Actually... a lot of the following is wrong. We should have this PER atomic value, and perhaps ONLY for paths!
152
-
153
- Base code
154
- reader
155
- let viewTime = 0;
156
- for (let user of global().users) {
157
- viewTime = user.viewTime;
158
- }
159
- return viewTime;
160
- writer
161
- for (let [video, viewTime] of viewedVideos) {
162
- global().users[userId].viewTime += viewTime;
163
- }
164
-
165
- Schema optimization
166
- writer
167
- let changeStream = new SchemaPath(() => global().users[wildcard].viewTime);
168
- write(() => {
169
- for (let [video, viewTime] of viewedVideos) {
170
- changeStream.write(userId, viewTime);
171
- }
172
- })
173
- reader
174
- let schemaWatcher = new SchemaPath(() => global().users[wildcard].viewTime);
175
- watch(() => {
176
- let viewTime = 0;
177
- for (let [value, userId] of schemaWatcher.getValues()) {
178
- viewTime += value;
179
- }
180
- return viewTime;
181
- });
182
- reader specific path
183
- // I guess we have to support gets as well. It should still be faster, as the schema is pre-defined
184
- let viewTimeWatcher = new SchemaPath(() => global().users[wildcard].viewTime);
185
- let factorWatcher = new SchemaPath(() => global().users[wildcard].factor);
186
- watch(() => {
187
- let viewTime = 0;
188
- for (let [value, userId] of schemaWatcher.getValues()) {
189
- let factor = factorWatcher.get(userId);
190
- viewTime += value * factor;
191
- }
192
- return viewTime;
193
- });
194
- delta reader (eventually)
195
- // This can easily be extrapolated to just getting deltas
196
- let deltaWatcher = new DeltaWatcher(() => global().users[wildcard].viewTime);
197
- let viewTime = 0;
198
- onDelta(() => {
199
- for (let [newValue, prevValue, userId] of deltaWatcher.getChanges()) {
200
- viewTime += newValue - prevValue;
201
- }
202
- return viewTime;
203
- });
204
-
205
- - Can be binary, or not.
206
- - Streams can work with non-streams, and vice versa.
207
- - The streams need to be setup in a tree, so we can efficiently check for watchers of them
208
- - We also need to support partial key watching. Often we will watch a few keys (and then within them, maybe all keys at another level).
209
-
210
- 1) START by supporting write streams (but NOT read schemas), as this allows us to define our schemas.
211
- IMPORTANT! Actually... a lot of the following is wrong. We should have this PER atomic value, and perhaps ONLY for paths!
212
- - This will give us a big chunk, which we will pass around (even passing around arrays of chunks).
213
- - The core will break this apart somewhat, with an object per schema, and then a tree of maps for the dynamicValues inside of it (and global lookups for the locks and values)
214
- - We will never create this from PathValues, instead, we will append values to specific schemas as we build it
215
- - And... locks need to be kept track of as well
216
- - So we need a global "captureWrites", to set the state
217
- - It will return a function, which will take parameter to finish the writes?
218
- - Or... something. We need to look at proxyWatcher and see what the best way to do this is. I think it might check the values before finishing them?
219
- - The schema builder functions (on the object returned by defining the schema), will then internally add to a lookup in the globally capturing state (keys by the schema seqNum)
220
- - Directly adding to the PathValue for that schema
221
- - A lot of fields will only be set on finish
222
- - I think we still need to support the helper flags (.valid). Which is actually fine, even if it's binary. It's fairly easy to flip flags in binary data...
223
- - When we need to reason about PathValues independently we can provide some kind of pointer than can be used in conjuction with the chunk
224
- - Although most of the time we will just provide iterators to iterate over all the PathValues?
225
- - I guess eventually the pointer could be serializable too, so that we could select PathValues with a Chunk + Buffer containing pointers, so we entirely manage our own memory. But... probably not for a while, as { schemaId: number; index: number }[] should be VERY efficient to allocate and store, especially if it isn't persisted.
226
- // PathValue
227
- {
228
- schema: ({
229
- type: "constant";
230
- key: string | number;
231
- } | {
232
- type: "dynamic";
233
- })[];
234
- values: {
235
- dynamicValues: (string | number)[];
236
- // Pointer to position in value Buffer
237
- setValue: Pointer;
238
- // SeqNum to ReadLockGroup (as often many values will have the same set of ReadLocks)
239
- readLocks: Pointer;
240
- ...more fields for various PathValue fields
241
- // NOTE: ReadLocks need to reference another binary structure
242
- // - Probably via a locally unique seqNum, which is remapped upon receiving data over the network
243
- }[];
244
- }[]
245
- // ReadLockGroups
246
- {
247
- seqNum: number;
248
- lockSeqNums: {
249
- schemaSeqNum: number;
250
- seqNum: number;
251
- }[];
252
- }[]
253
- // ReadLocks
254
- {
255
- seqNum: number;
256
- schema: ({
257
- type: "constant";
258
- key: string | number;
259
- } | {
260
- type: "dynamic";
261
- })[];
262
- locks: {
263
- seqNum: number;
264
- dynamicValues: (string | number)[];
265
- startTime, endTime, flags, ...;
266
- }[];
267
- }[]
268
- // Values
269
- {
270
- seqNum: number;
271
- // arbitrary binary data
272
- }[]
273
- 2) Isolate this object structure, so it is only manipulated or read via helpers, so we can change it to use a binary format later on
274
- - This means adding functions to go to/from binary, even at the start, and just having it go to PathValue and use PathValueSerializer for now.
275
- - We will have unique id (a number, locally unique) per schema, so we can very quickly tell if anything might match
276
- - OH! use global objects for schema defs, so equivalent schema defs share an object. This lets us store flags for them!
277
- - Not the whole schema, just the def part
278
- - Just a pointer as well
279
- - If we have conflicts with other schemas (now or in the future), this needs to point to the shared data for the combined schema.
280
- - Expose the shared objects as createdSharedAccessor<Object>(key) => { get(schema); set(schema, value) }
281
- 3) THEN, use this structure throughout, replacing PathValues in the entire application with lists of these chunks
282
- - We'll probably store in the core in an entirely splayed manner. Maybe the schema, then a tree of dynamic values with the leaves being the set value?
283
- - The set value will contain much of the PathValue, excluding the path
284
- - Update the core code to store data in schemas
285
- - If schemas conflict, we take the more general union of them. Annoying, but this should be rare, as schemas aren't recursive.
286
- - Ex, "x.*.z" and "x.y.*" NEED to be combined to "x.*.*", at least for storage.
287
- - AND, we need to store the original schemas, and have them MAP to the combined schema!
288
- - So we end up with a non-schema data storage, and a bunch of schemas
289
- - TWO ways to access data in schemas
290
- - If you have a schema, you just directly lookup your data in the shared schema data, which is owned by the core, but every schema points to it
291
- - If you don't have a schema...
292
- - We need a lookup of schemas as values with wildcard (I guess empty keys equals a wildcard? Hmm...)
293
- - I think we disallow reading empty keys? If not... maybe we should? They are already used a wildcard watches anyways...
294
- - Check each part of the path, with wildcard filling the rest "x.y.z" => ["*.*.*", "x.*.*", "x.y.*", "x.y.z"]
295
- - OH! That's not enough. Because... "x.*.z" is a valid path, which won't match any of them.
296
- - So... maybe a sorted list?
297
- - Should be reasonably fast...|
298
- 4) Then update it to be binary
299
- - On write
300
- - And on network traversal
301
- - I think PathValueSerializer needs to be rewritten. Basically, it will be given schemas ahead of time, sometimes (or maybe always, as we might as well always encode in our base schema), and then encode the paths more efficiently.
302
- - OH! ReadLocks need some kind of schema as well. Hmm... I guess, they can use the schema of their readers?
303
- 5) ALSO, create some kind of global string lookup? A lot of string keys will be the same, and it's easier to manipulate/store numbers
304
- - We will need to provide definition when we send them over the wire though
305
- - THIS is IMPORTANT! Without this our strings become inefficient, and the fastest code would involve mapping all strings (such as userIds), to numbers at an application level, which is a lot of work that the framework should really handle...
306
- - Maybe not for ALL string keys? Hmm...
307
- - OH! And... they only need to be unique per schema? Hmm...
308
-
309
- - Test with structures like "components.*.x" (but lots of different x values)
310
- - Hmm... the big speed issue is that the component ids don't map to an array. Although, in theory, we could do that, remapping ids to indexes. And then... reuse indexes on gc, so they become direct offsets into binary memory.
311
- - Then... I guess we can remember the last mapping, and so quickly go from string to index, with just a === on the strings (which should just be a pointer comparison), which... and even though the index is dynamic, that's still basically just a pointer. So... we could recover a lot of the speed
312
- - Maybe we should try this out in a mini-benchmark, with a greatly simplified structure (values constant size, no extra fields, all schemas are just 3 long and have similar structure)?
313
- - AH! Have the schema context wrapper (so schema reads/write get registered), give us a context object, which it gives back for the same type of access.
314
- - THEN, we can have state per watcher, and... maybe per watcher+schema. Then we can use this to cache the last keys at indexes per schema, per watcher... and for component accesses... this will always be the same, making the mapping instant every time
315
- 6) Support reading from schemas as well
316
- - If the data is already stored in the schema format, querying in this format should be very fast
317
- - We will need a shorter read binary format, which has a schema and dynamic values
318
- 7) AUTOMATIC schema definition generation from createSchema (replacing the code), and replacing (where possible) the uses of the schema to use the schema definition
319
- - Probably the hardest part, but... the most important. Without this our application code becomes too difficult to use
320
- - Nested accesses become difficult, but... not impossible.
321
- - Object assignment needs to become global
322
- Local assignment
323
- let list = data().list;
324
- for (let datum of newData) {
325
- list[datum.key] = datum.value;
326
- }
327
- Global assigment
328
- let list = data().list;
329
- for (let datum of newData) {
330
- data().list[datum.key] = datum.value;
331
- }
332
- Schema based
333
- let list = data().list;
334
- for (let datum of newData) {
335
- listSchema.write(datum.key, datum.value);
336
- }
337
-
338
- Reads are more likely to use highly nested assignment... even cross function
339
- - Cross function stuff is hard.
340
- - We might need to specialize functions?
341
- - As in, per type of possible schema input object, make a new function, and still leave a non-schema function.
342
- - Actually... this should be fairly easy. We aren't going to use === on the functions... ever? And if we do... then I guess it'll fail (or they can mark them as non-specializable, or... we can detect if === is ever used on the function).
343
-
344
- 8) Delta watchers
345
- - If we store change reasons in the schema format... then we can query deltas for free
346
- - Stored in the packed format.
347
-
348
- Global nested object state for latest values
349
- - Can have multiple global objects, maybe one per collection / schema?
350
- - We still need to register reads
351
- - If we can get code to not write duplicate reads (which a lot of code can be verified to not do anyways), then tracking the reads becomes very simple with our binary schema data, basically just involving writing numbers (or even just the start/end of ranges) to an output Buffer.
352
-
353
- Code transformation for accesses
354
- - Add code which can parse a function, find any schema accesses (this requires walking the type tree, and maybe checking other files), and transform the code to call data.q() functions instead (if the schema is a schema2 schema)
355
- - Add support for __callerFileName__, etc so this function can determine the caller file, so it can know how to parse it.
356
- - At first do this via a stand-alone function call?
357
- - And then make it a parameter on the schema?
358
- - OR, should we transform the code directly?
359
- - If we do it at runtime, we have to figure out capturing scope again. This is hard, and breaks static variables, but... maybe that's okay?
360
- - We could always warn about static variables
361
- - For any non-static variables (constants, functions, etc), we will need to duplicate them, and anything they access, etc).
362
- - THIS is actually REALLY nice. We can add a file name for this so it can be discoverable in devtools, or, the user can set `debugger;`, at which point, they will see a file which just has the function and anything it depends on. This makes debugging easier, as it removes everything not involved in that function (and more importantly, shows everything that IS involved, which could be surprising, as you might miss the fact that certain functions are being called, but if their definition is there you are far less likely to miss it).
363
-
364
- Code transformation to turn types into schemas?
365
- - If we could do this... we could get fast behavior from normal typescript code
366
- - We would need an out in case the code accesses the schema in a non-schema type way (just for accesses, the backend should already be able to handle this).
367
-
368
- Binary ValuePaths, which are never decoded
369
- 1) Allowing accessing a value in a way which returns the intermediate deserialized object (the Buffer[], strings lookup, etc) PLUS the id (or an object?).
370
- - Call it, ValuePathBinary
371
- 2) Allow using ValuePathBinary as an input to SchemaPath parameters, updating the SchemaPath code to directly copy the binary data
372
- 3) Create a function which exclusively accesses ValuePathBinary, using it for nested sets (getting a value, using that to index another value, etc)
373
- - Verify we are never actually decoding the values
374
- 4) Hack together something where we can NOT decode strings in the ValuePath serializer, sometimes, as our special function will never use strings directly.
375
- 5) Benchmark to see the benefit (probably via memory pressure), or not decoding the strings?
376
-
377
- Automatic commit wrapping of local synced writes
378
- - If we write or read from local synced state... and are not in a transaction, we should start a transaction, which exists behind the end of the call, and is only stopped:
379
- a) After Promise().finally()
380
- b) When any non-local synced state is accessed (which will cause us to throw as well)
381
- c) When any transactions are explicitly started
382
- - This resolves the issue of having to constantly wrap state writes, which is just annoying
383
- - We SHOULD wrap this into a singleton local watcher which we re-use for local events ("once" watchers). This will make events faster, and should make it easier to implement.
384
- ALSO
385
- - Allow accessing local state values without being in a synced state?
386
-
387
- Server crash log protection
388
- - Up to 10 times per hour, synchronously write unclassified logs to special single log blocks
389
- - We will delete these special blocks when we finally write the logs
390
- - This way if we get a fatal error, the console.error before we crash (which HOPEFULLY) we get, will let us know what went wrong.
391
- - OR... maybe not... maybe the server manager should just watch for crashes and log the most recent stdout/stderr logs?
392
-
393
- PathValueController direct remote database support
394
- --ext-remote "D:/code/other/loader.ts"
395
- {
396
- /** undefined means it doesn't need to be loaded remotely */
397
- loadRemoteValue(path): Promise<unknown | undefined> | undefined;
398
- }
399
- We'll probably have a timeout, just providing epochValues if loadRemoteValue takes more than a few seconds
400
- We COULD allow for core to be resolved to the same thing independent of version (by making the core register itself globally, and all cores to use the same thing).
401
- - AND, we could do the same thing for a few other files, such as clientWatcher, etc
402
- - This would allow clientWatcher.setValues to be called explicitly
403
- - This MIGHT be useful, but... then again... it adds a lot of complexity
404
-
405
- Hot render throttle
406
- - We want all inputs to be hot, BUT, to not render on each keydown. So... we need to delay rendering triggering?
407
- - BUT, we run into issues if a button then runs, which expects to have the most recent data closed upon? Ugh...
408
-
409
- Auto commit mode
410
- - If anything is accessed outside of synced state, enter a commit, and end that commit when:
411
- a) Promise.resolve().finally
412
- b) We try to end any other commit
413
- - Should be a Querysub config value which is on by default
414
- - VERY useful clientside
415
- - We need to support "lazyClose" watcher in proxyWatcher, that DOESN'T reset the runningWatcher UNTIL it is explicitly reset, OR, until another watcher tries to run
416
- - We need to detect synchronized accesses in proxyWatcher and start this special type of commit
417
- - We need to know if we are in this state, and THROW if we access any non-synced paths
418
- - This is mostly for non-local paths
419
- - Throwing WHEN they paths are accesses is required, otherwise the error location is too annoying
420
-
421
- Watcher diff mode
422
- - Running stress tests (satTest.ts) with a single watcher shows that the slowest part is our function watcher, specifically all of the parts that don't operate in a delta mode (ex, setWatches).
423
- - If we made a watcher fully support a delta mode (which is fairly easy, as it converts all the changes to deltas anyways), the watcher would likely work MUCH faster.
424
- - ClientWatcher needs to expose a delta interface
425
- - proxyWatcher also needs a delta mode
426
- - Rerun the benchmark after using the for FunctionRunner, and our call/s should go up significantly
427
-
428
- Shard display / manual sharding / automatic sharding
429
- - Sharding will be fixed on process start
430
- - We can augment our database path size distribution display to also support explicit sharding control
431
- - This means we have all machines register, and then tell them what pathAuthorities to run (generally just running a single process, but always a new process, with us never changing pathAuthorities for a process/node)
432
- - THEN, we can write code that automatically sets the sharding configuration, not changing it too often, but keeping everything fairly well distributed
433
- - Our merging should automatically handle cleaning up dead nodes, so we really just need to create and kill processes to control sharding.
434
-
435
- Function implementation benchmarking / profiling / diagnostics
436
- - Have a debug page which shows all functions that have been run, with diagnostics
437
- - Time taken (total, average, etc)
438
- - Lock count
439
- - Rerun count
440
- - Really important, as it is easy to right functions with a high rerun count. Ideally every function should run at most 2 times, once to know which path it requires, and again once those are all synced.
441
- - Reject %
442
-
443
- Fix wildcard permissions
444
- - Wildcard values should do more than just check "", they should check all possible direct permission keys
445
- - This fixes the `{ admin: { PERMISSIONS(){ return users()[config.callerId].isAdmin; } } }` type of permission check
446
- - Also `{ serviceSecrets: { PERMISSIONS(){ return false; } } }`
447
- - It isn't SO bad, as Object.values() only provides shallow values, but... we should still fix it.
448
-
449
- Browser local PathValue caching
450
- - https://rxdb.info/rx-storage-opfs.html
451
- - Do it at a core level
452
- - Start with being able to enable it for specific paths, triggered by a flag to createLocalSchema which causes that entire schema to be synced.
453
- - Or... createLocalSyncedSchema?
454
- - Eventually add support for remote paths, but in a way to not erronously trigger the sync flag
455
- - We need to batch the PathValues that we store, and load them in efficiently, etc, etc
456
-
457
- Combined console/event display
458
- - Everything grouped by event, with minor sorting based on the latest events, but it is mostly just an aggregator
459
- - Show the most recent events
460
- - Allow drilling into a specific event, searching it, filtering it, etc
461
- - Allow setting up debug workflows
462
- - Take a specific event / search, run some code on it to filter and create another search, which gets values, which we then can run another set of code on, etc, cascading
463
- - Tree summarization / navigation
464
- - For tree display, take any node that has > N children (maybe 100?), and wildcard it, collapsing all children into a single node (and merging all child tree, etc, basically rewriting it so instead of many keys, it has exactly 1 key). This should give us a small number of finite paths. Then we can drill down far enough to split each node into large enough chunks (so we aren’t only looking at roots, but also not only looking at children), with the split factor being configurable (so we CAN just look at root, or just look really high level).
465
-
466
- Heap analyzer
467
- - Start by showing shallow size with a depth of 1, and then allow clicking to add more depth
468
- - Maybe allow viewing the options at each level (instead of just taking the first reference)?
469
-
470
- Better serverside logging
471
- - Batch it or something, so 20 requests per second (which isn't even that much), doesn't cause the console to be unusable.
472
- - Maybe... group by type, and show all the categories on the screen at once, allowing the user to type into the console to expand a group, pin a group, filter, etc
473
- - The most recent groups can be shown first, WITH, extra prioritization for severity
474
- - Expanding a group will show the specific entries (otherwise we just show the a count, and MAYBE the latest value?)
475
- - Filtering would probably be useful too
476
- - OR... we could just show a really simple log, and link to a web UI?
477
- - In the web UI we could show logs for multiple processes too!
478
- - We could tag groups with processes that contribute to them
479
- - And also show active processes, and recently terminated processes
480
-
481
- Cloudflare bootstrap caching
482
- - Add round robin DNS entries (or just verify we are already using it), for our root domain, and turn on some level of caching
483
- - I think we return the correct e-tag, so we should be able to cache fairly heavily
484
- - If we turn the file download to be 2 stage we can cache the second stage forever (including the hashes we obtained from the first stage), which... SHOULD be a lot faster (the first call will be slower, and two calls are required, but the second call should be always cached in cloudflare, and then in the user's browser).
485
- - Fix clientside node routing
486
- - Update our certificate generation to have one level of wildcard.
487
- - I believe (we should verify this), you can't have multiple levels of wildcards, so... this certificate WON'T be able to impersonate our full node domains (which are 3 parts), and even if it can... we do extra verification of the public key, so... it would actually be fine.
488
- - Publish A records for our machines
489
- - Convert the node ids to just use the machine id clientside, allowing the client to pick any Querysub node
490
- - Have the node list accessed via HTTP, and cached for a few minutes, as it will change constantly, but... clients don't need the latest version
491
- - I GUESS after the first read, if we can't find a Querysub node, we can add some flag to disable caching for it, which would eliminate the lag between fixing all the servers and the site being usable again
492
-
493
- Querysub node sharding
494
- - Presently we allow any client to use any Querysub node. We might want to shard them too? Or at least make it allowed?
495
-
496
- Archives disk caching
497
- - Cache backblaze files on disk.
498
- - Only files that are at the file size limit and old enough (coordinate with the thresholds the merger uses).
499
- - At a certain point the merger will stop touching files, or touch them less often, and so we will be able to cache them on our disk for a while.
500
- - Limit our size to a fixed disk % (probably 5%?), with a configurable list of disks to use
501
-
502
- Clientside sourcemaps in error callstacks (maybe just have Querysub install the error stack library by default, in the browser?)
503
-
504
- typenode improvements
505
- - Log typenode timings after setImmediate, top 10 + remaining timing, in a table
506
- - Also, maybe entries with time > 10ms (including remaining), and not logging the table if this would make it empty.
507
-
508
- Tool to suggest balanced path ranges (based on data size, not access patterns).
509
- - We can even just have it be a function, which we call on startup (telling it the count of nodes, and which index we are), and which caches the shards. It isn't live automatic sharding, but it is technically offline automatic sharding, which isn't too bad.
510
-
511
- FunctionRunner automatic balancing
512
- - Exposed functions will also define define their shard distribution.
513
- - Ex, setShardDistribution(addToUserBalance, (userId, balance) => userId)
514
- - It is somewhat important for the shard distribution to be consistent across various functions.
515
- - If this is just set on our most commonly called function, sharding should work quite well
516
- - IF a shard becomes too far behind, other nodes will pick up the slack, with the closest shards picking it up first, then those farther away, etc, etc.
517
- - Hopefully we can use the shard values to prevent too much duplicate work, even without any direct communication between the nodes
518
-
519
- Turn on and test backblaze storage
520
- - Create a utility to go from disk => backblaze (and might as well go from backblaze => disk)
521
- - Test starting a server on another machine
522
- - After setting up the .json keys files, it... should just work?
523
-
524
- Anti-rejection code (isn't REQUIRED to make the database useful, so we should wait. Would be pretty slick though... Ideally this can all be extensions that have no or only modular impact on the core functions, or even no or modular impact on the proxy? Although that might not be possible...)
525
- Summary
526
- Excess work (N^2) due to rejections
527
- - Past time reading (removes causality guarantee, almost completely preventing rejections)
528
- - Unsafe reads (to remove locks, and then selectively add them back)
529
- Jitter (due to rejections)
530
- - Pase time reading (even further in the past than required, to allow a buffer of values)
531
- Jitter (due to varying client lag)
532
- - Past time reading
533
- Lag makes player near impossible to kill
534
- - Adjust writeTime to stay > min value, depending on the values changed (some values have to be < 100ms, etc)
535
- Laggy player does things only they can see
536
- - Adjust age of past dependencies to stay > min value, depending on the values changed (some values have to be < 100ms, etc)
537
- Clientside operation lag resulting in inability to perform delicate operations (ex, shots miss)
538
- - Past time reading
539
- Clientside operations applied out of order
540
- - More consistently adjust writeTimes (instead of one some functions but not others)
541
- Operation cause-and-effect no ordered (phasing through objects)
542
- - Stop using past time reading
543
- Clientside hacking script to automatically react to values in the past
544
- - Serverside changing of time, rejecting clientside predictions, AND, something to tween to the correct state, to prevent snapping
545
-
546
- todonext
547
- brainstorm: `So what is the FAIREST way to prevent lag from HELPING.`
548
-
549
- == Test app, with top down "shooter"? (with just circles and lines?)
550
- - Movement will be "key" based, not frame based. This is harder to implement, but takes a lot of pressure off of the synchronization code.
551
- - And... the look position will be... just blurred, using maybe 3 ticks per second? Ugh... that will work, for now. Eventually the synchronization code SHOULD be fast enough to handle 30 ticks per second, but presently... our overhead is just too high, and so operations that SHOULD take ~10ns take ~10us, and there's not much we can do about it until we use deltas everywhere, and replace our usage of proxies.
552
- - This makes the physics system harder to calculate. Although, not that much harder, as we can still use the "intersection" approach, if we want to, by locally emitting positions per frame.
553
- - I'm not sure the best way to do it... intersections (which really decomposes into line segments) is nice, as it allows infinite precision.
554
- - Of course, if we have any concept of "gravity", then it is harder. BUT, even then, we can still extrapolate, and even better, we can extrapolate with non-linear segments, which is really the best way to do those kind of simulations anyways.
555
- - We WOULD require a way to make the non-linear segments update consistently, but... as we have a universal time, that wouldn't be so hard...
556
- - WELL, no matter what we need some kind of snapshots, to prevent having to re-simulate TOO much state.
557
- - So... we can have "keys" only last for a certain period of time, and after that, movement stops. Of course, if we had gravity this would make it possible to freeze in mid-air, but, eh... it is what it is.
558
- - AND, if we had any NPCs or AI, those would require an app server, or some kind of general purpose interval function calling server anyways...
559
- == Add AI to automatically move around nodes, and automatically shoot
560
- == Add multiple nodes, with various latencies, which flucuate with different magititudes
561
- - We should see nodes jumping around
562
- - It will be hard to hit laggy nodes, as you don't know where they are!
563
-
564
- Delayed prediction rejection
565
- - Not sure if this is needed, but in theory if there is contention a function might always require multiple runs to be correct. This means our prediction will always be rejected. BUT, it is probably pretty close, so... we should just keep it around for a bit, until FunctionRunner finishes up the function, giving us the most up to date state.
566
-
567
- Past time client reading
568
- - Intentionally read client positions older that the latest, in order to cause a smoother state.
569
- - Will read a combination of a fraction + value in the past (so clients that are 1000ms +/- 500ms
570
- still look like they are smooth)
571
- - The readLocks will have to have an empty time range (as we KNOW their range will have invalid values)
572
- - At least for write values. For the UI, this doesn't matter, the UI is readonly
573
- - Bakes in these values to the function call when asking the server to run the function
574
- - When we evaluate the function call, if those paths are read, we have to adjust the read times
575
- - OF COURSE, only if those paths are read with the request for "past time client reading"
576
- - Allow CUSTOM limiting (we always limit to a few minutes), of time in the past, adjusting the read to be more recent if it is too old (by the Querysub, on received time)
577
- - This is important in competitive games to prevent things from undoing a lot of other state, just because one client is lagging badly.
578
- - TWO settings, one on a global level, and another on a path level, inside of FunctionRunner (using the writeTime as the base)
579
- - MAYBE we actually limit read times based on the writeTime, which we can use to estimate the lag. Ex, they can only read back as far as 4X their current write lag? The default won't be to do this check, but... for competitive situations we do want to do it...
580
- - The writeTime will still be the client writeTime, so by default we preserve client side-effect ordering (ex, cast "shield", then "fire", so the shield protects against the fire)
581
-
582
- ++ Fixes nodes jumping around, as well as making it hard to hit laggy nodes
583
-
584
- == Add a check so that dead nodes can't shoot anyone (which we might already have as check?)
585
- == Laggy clients will be hard to kill, because by the time they know they are dead, they will have killed their attacker in the past, so they won't be dead anymore!
586
-
587
- Ignored client write times?
588
- - With support of "past time reads", we can always change the writeTime (somewhat, we will have to add a special case where all reads are now forced to be past time), without causing a rejection.
589
- - This would need to be down on a function level (instead of on a path value)
590
- - This would allowing resurrecting bad values, as well as changing the order of effects (ex, shield, then cast fire. BUT, if shield is run at server write time, it could happen after the fire, and so the fire could affect you even though you KNOW the order was correct).
591
- - Which... is fine, we just have to make it clear that this reorders the function implications
592
- - ALSO, make it clear that this can cause REALLY bad rejections. So if this is done with movement... the client better have really good rejection handling, or it will jitter like crazy
593
- - ALSO, if the time of our write is important (ex, if we store keys, and then your position is your timeDelta * velocity), then this breaks that (so it isn't just order sensitive, but time sensitive values it impacts)
594
- Expose writeTime to functions
595
- - For example, so they can give some leeway in deaths:
596
- `if (isDead && isDead < writeTime - 50ms) return "deadCantShoot"`
597
-
598
- ++ Fixes making it hard to kill laggy nodes (by making them shoot at the serverTime, not the clientTime, so they can't surprise clients with "you're dead")
599
-
600
- == Add "gravity" power, which either accelerates all users towards or away from the user
601
- == ACTUALLY, I think the problem will be drift. If we are constantly writing, and assuming our prediction are correct, and having them read the previous predictions, we can get really far off from the server
602
-
603
- Client cascading call resimulation
604
- - To prevent drift, we can rerun client calls after a rejection. This is a bit annoying, and requires some way to hook directly into rejections (which... actually isn't that hard?)
605
- - Basically, instead of clobbering all ReadLocks on call predictions, we identify any ReadLocks on other calls, and then watch them explicitly for rejections. If they reject, we re-simulate our call, writing in such a way that we force our previous call to be rejected (which then triggers ourself for future calls, etc, etc).
606
- - We might need to write to versions MUCH farther in the past, as now we need to increment the version, so... yeah...
607
- - ONLY if we don't have our result, of course, otherwise we would have no reason to run...
608
- - Probably do this in a batch, which considers all calls after rejections, then deep compares values, then follows the call chain to get all candidates for rerunning (and the order they should rerun)
609
- - We then need to wrote the original PathValues per result, which... is fine.
610
- - ALWAYS update all the way to the latest, OR, doesn't update.
611
- - Add a kind of throttling so we don't always update to the latest, to prevent lag causing huge amounts of cpu work. This will cause large "snaps" when we do resimulate, but... it will prevent the browser from locking up for too long?
612
- - Set a constant factor (maybe... 60? as 60 * 16 is close to 1000?), of our extra call rate. Then it is relatively easy to throttle, basically, every resolved call value (they are all rejections) adds +60, and every rerun (each value different causes reruns, but can cause many, if there are multiple calls that depend on it) takes away 1 per call.
613
- -Make sure to measure this time too, as it will be interesting to see how much time it takes
614
-
615
- ++ Fixes drift
616
-
617
- == Will there be a "jump" when a user stops toggling their power? If we are only looking at their keystrokes in the past, our predictions will always be off, but it should be relatively smooth?
618
-
619
- == Add collisions
620
- == After having very few users this becomes intractable, due to the inefficiencies of calculating collisions
621
-
622
- PHYSICS HELPER
623
- - Takes many path+shape+times as input to seed the data
624
- - Expose a function which takes a shape, and a range of times (as we don't know which path we are reading, so we don't know which past time to use), and gives all paths which might be close to it
625
- - We then take the paths that MIGHT intersect, and do full collision checks
626
- - We can do it with "past time" reads or not
627
- - "past time" reads allows object to phase through each other
628
- - regular reads causes more rejections, which can cause more server load, and clientside jitter
629
- - We should make it so one half of the world does regular reads, and the other does "past reads", and see the differences
630
- - Well, the difference should be, the "past time" world has no backtracking, so if you do a jump puzzle on your end, you did it. BUT, the "past time" world will also have more objects phasing through each other, and you will see other clients jumping on invisible platforms, etc, etc.
631
- - The "regular time" world will be more consistent, but sometimes you will swear you did something (jumping on a platform), but then you will backtrack and fall through it, because it WAS broken before you could jump on it.
632
- *** Implement Local cache helper ***
633
- - If we create a watchFunction with proxyWatcher, we can make the output synchronously accessible to other functions, as a way to allow unsafe reads
634
- - If anything accesses this AND the accessed watcher has never been fully up to date (if it has been, but now isn't... allow it, I guess?), then the accessor is not fully up to date as well. It doesn't tracks it as a dependency, but instead as a "watch"
635
- - AND, when the cache is up to date, the watcher will rerun
636
- - AND, if the server does the same thing, we will USUALLY result in similar values, BUT, when we don't the server only runs the function once, so it will decide the final result.
637
- - For things that matter, such as shooting a player, we will want actually dependencies, but this can be used to limit the things we access which we have to depend on
638
- - Can lazily run, and just uses the database, so it will run in isolation fairly easily
639
- - When accessing the result, sometimes... register the accessed values as being used (so they aren't unsynced). This might be annoying, and require some batching / delay, but... it is very important!
640
-
641
- ++ Makes system a lot faster, by reducing rejections that don't change the value, and by increasing the efficiency of comparisons
642
-
643
- Automatic large value storage separation
644
- - If a value is > ~10MB, when we write it to archives, we should ALWAYS break it into another file
645
- - Have this decided via a flag on the PathValue, which can be dynamically set via size, or explicitly set.
646
- - We should lazily read these values
647
- - Garbage collection them after a while, when the file referencing them disappears
648
- - We should create a cache of these on disk as well, so even if we do need to read the values, we can often just risk off of the local disk
649
- - This will be annoying, but... if we already have disk value offloading, it shouldn't be so bad... AND, could really reduce startup time
650
-
651
- Archives disk cache value separation
652
- - If we already support large value separation, we can also separate values from archive value disk cache
653
- - STILL via some limit
654
- - Because it is on our own disk, we can seek inside the files, reading JUST the part we want, so the limit can be much lower, probably 1KB?
655
- - This can reduce startup time even further
656
-
657
- Network visualization
658
- - NONE of this will use our PathValue system, and will instead use specialized instrumentation functions
659
- - Will still be realtime though, and show paths, etc
660
- - The nodes, and paths, in the browser, with information on time alive, etc
661
- - Traffic information
662
-
663
- Multi domain support
664
- NOTE: Special case LOCAL_DOMAIN, so that we: 1) don't trust anyone else's LOCAL_DOMAIN, and 2) assume no one else trusts our LOCAL_DOMAIN
665
- - If you call a function on a cross domain, it won't execute inline, but instead result in a write
666
- - rootCertDomain needs to be removed and updated with a dynamic domain
667
- - NetworkTrustController needs to maintain trust per domain
668
- - Only allow readLocks on paths in trusted domains (otherwise throw, not even committing the write).
669
- - Writes to domains that don't trust us readLocks sanitized, so they don't depend on any of our domain values, otherwise our writes will become rejected immediately
670
- - Ensure we are still ignoring writes from nodes that aren't the authority on them (I think we are, but this becomes even more important, as now we might partially trust a node, accepting some values, but not others)?
671
-
672
- Querysub bootstrapper + git repo file server + local file system server
673
- - Basically... instead of requiring a static file server, we can have Querysub automatically host the files / repos based on some data written during deploy (probably specified via a function that is called in something the deploy.ts imports.)
674
- - Querysub won't even know which servers it is hosting, and just host a generic bootstrapper, which when run on the client will just do a regular data access with the domain + path to get the entry point
675
- - Querysub will basically just have a utility to go from gitrepo => file WITH, load development file system support.
676
-
677
-
678
- Automatic schema usage/generation
679
- - Parse the types to generate the schema, then automatically update accesses to use the schema object
680
-
681
- PathValue.value disk cache
682
- - If an entire server (PathValue server and Querysub) NEVER uses path values, we can move PathValue.values onto the disk, only reading them off disk when we send values over the wire
683
- - This will have to be done explicitly, via a setting in AuthorityPathValueStorage
684
- - The values will be read off disk by our serializer, which will ask AuthorityPathValueStorage to read all the real values off disk
685
- - The FunctionRunner SHOULDN'T need this, as all of the values it stores should have been used semi-recently...
686
- - Actually, only really only the PathValue server requires this, as otherwise we discard values after ClientWatcher.WATCH_STICK_TIME, which is presently 10 seconds!!!??? But even at a few minutes, that value won't be too low. What are we going to do, render 2GB of images at once? (and if it is video we need to stream it anyways, likely from another storage source, as there is no reason to use PathValues for videos...)
687
-
688
- Optimizations
689
- - Client storage of values, allowing the server to only send Time for large value the client doesn't have
690
- - The client can just skip adding the PathValue, BUT, keep track of the last thing that is waiting to add. Then if the value is superceded anyway, it just won't add the full value, after it receives it.
691
- - If the values are VERY large the server might be storing them on disk, so this allows the server to avoid even reading them off disk, which is extra efficient.
692
- - One time subscriptions + polling subscriptions
693
- - For large data that changes frequently this might be better?
694
- - Sending very large values seems to cause something to block for a really long time. Not sure what, but... sending 100K writes with writeOnly, and dontStorePredictions, locks up for minutes at a time, then allows a lot of values to get through, then locks up?
695
- - 10K writes is a lot better, and 1K writes is really fast.
696
- - Maybe... writes on the websocket are buffered, and then the socket disconnects due to having too much send at once, and only reconnects later due to some random polling?
697
- - MAYBE we just need better server enforced throttling?
698
- - OR, if the problem are that the funtctions are getting rejected (which I am 99% is the problem), we should support "updateWriteTimeIfNeeded", and see if that fixes it?
699
- - AND, we also need to handle streaming the updates better in FunctionRunner. It is kind of just batching everything, when it needs to wait (maybe with an "io" delay), to let the outputs clear?
700
- - Delta supporting watcher
701
- - Take all places that iterate over all paths, and have them work on deltas
702
- - The callback will still have to process the changed paths directly (although it will have access to any state), and indicate the deltas to the watches
703
- - This could be used in FunctionRunner to make it more efficient
704
- - If we track when a path was created, wait longer to archive (2.5 times MAX_CHANGE_AGE), and then detect that is was deleted < MAX_CHANGE_AGE * 0.5 after it was created... we can avoid archiving it. But... then we need to clean up the tracking of when it was created, and that's a whole thing, so... it's just difficult...
705
- - The server should batch forwardWrites when it receives them. That way if it is lagging due to a slow (synchronous) operation, a lot of values will buffer up on the network (but the batch queue will be empty, as we will have synchronously emptied it), and then when we finish the request we will have the batch time to gather up all the network requests (which should be a lot! as we can handle a lot of bytes in even just 10ms!).
706
- - Of course, this adds ANOTHER 10ms delay, but... that should be fine, we won't get forwarded values often...
707
- - We don't need to send back predicted values, when the prediction is accepted
708
- - Client side we should only trigger watchers when a watched values actually changes (not just when there are any changes on the watches paths, at any point in history)
709
- - Client syncing thrashing
710
- - When we no longer need a value, we should stop immediately clearing it, and just mark it as unneeded, and then clear and unsync it a bit later on. This will take more memory, but allow navigation to be quite a bit faster
711
- - Optimize parent accesses to not do brute force `.startsWith` searches, and instead maintain some kind of a lookup?
712
- - Maybe... although, is this even much faster? It will certainly slow down writes.
713
- - Optimize readLocks
714
- - Each write stores readLocks very redundantly, making each function have `WRITES * READS` readLocks, which takes a lot to store, and requires a lot to synchronize.
715
- - We can probably store a lockHash, to quickly detect duplicates
716
- - Wire calls can use lockHash to depopulate duplicates, then re-populate them on the otherside.
717
- - Such as commitWrites/forwardWrites, etc
718
- - Our storage can depopulate duplicates when storing as well, maybe even storing readLocks in another file?
719
- - Clear old pathValueProxy cache
720
- - Better object comparison in certain values when we compare PathValue.value values? (only really clientside, I think?)
721
- - As well as more object comparison checks, to not notify if the value is set to an identical value.
722
- - Streaming / waiting during storage operations
723
- - For example, we can stringify the file in chunks, instead of all at once, that way we don't block for > 100ms. We should wait ~10ms, giving a lot of requests time to clear.
724
- - This isn't going to be especially big for large datasets, which may require merging everything at once, which with our current algorithm could take seconds!
725
- - More efficient wire size and serialization speed
726
- - If we write to 200K number values, which all depend on the previous states of 200K values, we end up sending 94MB, which takes 1200ms to serialize. We also receive a lot of data back for all the valid states. It might not be easy, but... it would be nice to optimize this, in some way.
727
- - Probably via schema handling?
728
- - ALSO, 94MB exceeds our default packet limit, so it doesn't even send! We could fix this with compression, but then it would take even longer to serialize!
729
-
730
- Forward retry code
731
- - We should maybe retry to forward writes if we can't forward them (and we aren't an authority)?
732
-
733
- Wait a bit before loading values, in case a server wrote values, died, but the remote storage system isn't serving the read back yet?
734
-
735
- Changing authorities bug
736
- - If an authority changes, it is possible for it to receive values it doesn't care about, and then respond saying the results are valid. However, they aren't, instead it just ignored the values.
737
- - ALSO, it is possible we will send a client ends up watching valid states from multiple authorities, due to being unsure if any are down, etc.
738
- - AND, we could end up subscribed to multiple authorities for a single path (due to watchValueLocks), which will result in thrashing.
739
-
740
- Prediction limiting / server overload protection
741
- - If the PathValue server gets too far behind, we should stop predicting values, and start waiting for updates until the server can catch up
742
- - This can also help when the server goes down, OR, more likely (hopefully), when the client's internet goes down.
743
- - ALSO, the first thing to try is really to try switching servers, which will give us a form of automatic load balancing
744
- - Although... if the limiting factor is our internet... then switching servers will actually make it worse. So... idk... this is actually difficult!
745
- - MAYBE the server has to ask us to switch servers, and all we do when we find slowness is to slow down our writes?
746
-
747
- Clientside function threading
748
- - Clientside it would be fantastic, as it would allow render functions to NOT block the render thread. This means we could run multiple threads, keeping the application responsive even if a very slow component is rendering.
749
- - We can convert dom event callbacks (or any closed values) to { functionContents, closedValues: { [variableName: string]: unknown } }. If the variable values are either serializable, OR, can be linked to exports (ex, misc_1.isEmpty), then we can recreate an identical function just by using eval.
750
- - If we do this, we would we ALWAYS want to recreate the function. This ensures there is consistency if we call the callback cross thread, or on the same thread.
751
- - We can store this style inside of the synced state, and make it available to be called directly.
752
-
753
- Serverside function threading
754
- - Required to kill very slow running functions
755
- - They will of course we able to change their default timeout time
756
- - They will need to be given an error result, to prevent them from rerunning
757
-
758
- Serverside rendering
759
- - Create a mounter which can go from vnode => string
760
- - Clientside, probably render from scratch, and just have our clientside mounter check for existing DOM nodes (probably just when instructed to, which will happen on initial render, maybe... always on initial render?)
761
- - Events COULD be an issue, or... we could just assume any unknown nodes have no event handlers?
762
- - I THINK we can probably ignore events before we render, at least for version 1. The app is mostly a SPA anyways, so the time before render should be VERY short, and then everything should involve re-renders and NOT re-navigations. For version 2 we can capture everything globally via a built in script tag, and then re-trigger them once we actually mount (and either block, or delay links, so we don't get in a state of constantly trying to load/render, getting 50% of the way there, but then the user navigates via a link).
763
-
764
- TESTS
765
- - Tests which track the code used so we know which tests need to be rerun
766
- - Render tests, with render JSX and paint it
767
- - Can use a partial JSX => paint cache, so most of the time if the output doesn't change, OR, if the output doesn't change enough to change how it is painted, we don't have to repaint it.
768
- - Also, a test harness that can click on vnodes and simulate their changes would be incredibly useful!
769
- - This would get rid of the chrome javascript runtime step. We might still use puppeteer, but also... it is possible we could just use blink, or some other library, to just go from HTML => image extremely fast (realistically most pages should take < 50ms to render)
770
- - True test coverage checking
771
- - NOOP expressions (x + y becomes 0, etc, a function call that returns a number becomes 0, etc, etc), and see if any test changes. If not... then that line does not have test coverage
772
-
773
- === Development tools feature complete | TASKS = 22 ===
774
-
775
- Latency / load weighted node selection
776
- - When they are multiple candidates, prefer the closer nodes
777
- - ALSO, prefer nodes that have less load, allowing a form of automatic network balancing
778
- - The ability to switch nodes would be nice as well
779
- - Switching PathValueController nodes should be easy, we just subscribe everything to a new node, then disconnect from the previous node.
780
- - We switch Querysub easily (using multiple), and disconnecting from the previous ones as soon as all calls on them have finished running (really that is true for most nodes, we don't want to disconnect when we have pending calls!)
781
-
782
- Speculative function calls (calling functions that haven't been requested yet, just to sync the state they might need)
783
- - Will be a bit involved, and might even require some clientside cooperation (ex, to detect which actions are possibly via which event handlers are being used), but... could result in a HUGE speed improvement
784
- - Will have to determine what functions are most likely to be called
785
- - We will likely need to give some context information for functions, or at least know their index, and maybe even page position? And maybe even the mouse position?
786
- - If we run functions calls when the user moves their mouse towards a button we can probably increase our estimate quality with little effort
787
- - We should test to see how long the mouse dwells over a button before clicking. Even 10ms would be useful. And also the average mouse speed. If we find users usually click after dwelling for say... 10ms, but take 20ms to click, we can get a 10ms headstart on the call!
788
- - Will re-render after the click and then pre-sync the new reads needed
789
- - If we do this on another thread, and somehow set the new syncs with low priority... this should be basically free (as if the user is moving their mouse around it isn't a background task, and we can at least monopolize a single core!)
790
- - Will also pre-populate the clientside function cache, so when they really click it we can get the prediction very fast
791
-
792
- Delta based render cache
793
- - The best idea is to mark reads as being delta allowed, and THEN, marking output values as being reduceable. Then we just run with partial values given to the read, and merge the new writes with the old writes. IF the writes differ in any places that are not reducable... then we have to run with the entire set of data (and it results in a warning). But if not... it just works!
794
- - The reduce functions can be
795
- We already know the delta when we re-run watchers (as we reran them for a reason!),
796
- - Maybe at the component level? Although... I kind of want to implement it for just arbitrary data
797
- - A function will have the ability to read from an object/list with a "delta" read
798
- - It will still receive the object/list, it might just partial
799
- - EXCEPT, we might need to return special state for deletions?
800
- - If the only reads of the object/list are delta, then the next time there is a change, only the new/changed values are provided
801
- - The output is analyzed, and the differences between the new output are found
802
- - For every different write, we determine the update technique
803
- - This will probably involve setting some special value on the write?
804
- - Examples would be "sum" or "join" or "insertSorted"
805
- - The most generic case receives the previous state (that has changed, already drilled down), and the new state, and updates the previous state (in memory) so it now has the new state as well.
806
- - We need to handle writes that propagate deletions
807
-
808
- Blocking FunctionRunner call mode
809
- - Will probably be slower in most cases though, as each value accesses required a round trip with an authority to read the value
810
- - Might be useful for code with expensive side-effects?
811
- - Add a BLOCKING mode to PathValueProxyWatcher, which uses Atomics.wait to block until a read value is synced.
812
- - This potentially allows for efficient function evaluation, by not requiring any code to rerun.
813
- - This is only possible if something can mark a value as synced on another thread, and absolutely not if we are single threaded (Atomics.wait wouldn't even work on the main thread of a single thread app anyways)
814
-
815
- Function results!
816
- - Specially marked "specializedHardwareCalls" functions will be able to "return" a result
817
- - Only possible with blocking FunctionRunner mode, as for the functions we want to return results we also only want to run them once (for expensive operations)
818
- - Of course, this doesn't prevent: 1) slowness cascasding syncing, or 2) result invalidation
819
- - Hmm... we also want the ability to cancel calls?
820
- - Maybe calls can do this without themselves?
821
- - Hmm... if the calls are so slow... maybe the user should just manage a queue themselves, so they can see what is going on? It really isn't that hard, and if they have to manage special hardware anyway... then it won't like this would prevent them from just quickly scaling a function?
822
- - At this point we should have support for unsafe function writes. We MIGHT want to make the writes from this unsafe by default?
823
- - By default probably allow write time adjustment, to prevent invalidation (at least when we start the function call)
824
- - The return object will be { type: Symbol("hardwareCall"), call: number, path: ".,querysub.com,.test" }
825
- - ALSO, unioned with a type for an error { type: "error", errorMessage: string; errorMessageFull: string; }?
826
- - The function return types will be modified so any callers have to handle this symbol type
827
- - The proxy watcher will temporarily register to receive all calls, so it can call them
828
- - The proxy watcher will search the output writes for these values
829
- - Only shallowly, which is fine for now, as if they nest it I believe the Symbol will cause a throw, so the wrong value won't be written and not noticed
830
- - The proxy watcher will then actually construct the call objects, telling them where to place their results
831
- - When the FunctionRunner evaluates the calls, it will place the results in the specified paths
832
-
833
- Support VERY large data sets (> local disk size)
834
- - If an authority hasn't been used for a while, dump the memory for it, and... become "unready". Then, if someone accesses it, go about loading it again.
835
- - This only works if we are sharded, but there isn't really a better way.
836
- - We should test this with some very large values.
837
-
838
- Automatic sharding of ValuePath nodes
839
- - For KVP nodes as well as Function nodes
840
- - More important for function nodes, but... it could help for KVP nodes as well?
841
- - IF we have high contention, and many servers, the resolving process takes SERVER_COUNT * SERVER_LATENCY, which could be high. Automatic balancing can help fix this, by finding a way to identify overlapping writes via reads (ex, spatially hash read paths, and if those hashes are ===, see if the spatial hash of the write paths are as well).
842
- - This reduces the SERVER_COUNT involved in contention, which... fixes the issue!
843
- - We will need to support remotePathAuthority to get this to work
844
- - Needs to be somewhat generic, so we can use it for both PathValueController, and FunctionRunnerController
845
- - Nodes will inform their parent path nodes of their utilization, so parent nodes can redistribute child nodes accordingly
846
- - Oversaturated nodes will split, pushing newer nodes down to more specific traffic
847
- - Ex, from ["x"] to ["x", "x.[0 <= hash < 0.5]"], ["x.[0.5 <= hash < 1]"]. AND, when a node has enough descendants, it will stop with all child paths, and the parent will just be ["x"]
848
- - BUT, we should only shard when needed. Otherwise syncing parents can become overly expensive? (Ex, if we have 100 servers, we really wouldn't want to split a small collection across 100 servers, as then it would take a huge amount of work to sync the keys, and if there are < 100 keys, it would be needlessly slow)
849
- - Nodes who have parents that have insufficient members will remerge with their parent (perhaps completely taking over the parent, if the parent completely disappears)
850
- - Nodes with child nodes with insufficient members will redistribute, or remerge into one path, as needed
851
- - Test on a local machine, spawning many processes, killing them, etc
852
- - We need to fix our path limitation thing
853
- - Detect the size limit of our underlying file system, and use that
854
- - This allows us to turn on long paths on windows
855
- - WARN if long paths are not enabled
856
- - Use this both to constrain balancing AND to have archives write to the most specific path possible, within the current limits
857
-
858
- Programmatic digital ocean droplet launching
859
- - We will want some kind of administration page that the app serves by default, requiring NetworkTrust and localhost to access.
860
- - From this page we should be able to:
861
- - import digital ocean api keys
862
- - see all currently configured droplets
863
- - launch new droplets
864
- - All launched droplets will be automatically configured with the code, given trust, and configured to auto restart all the necessary process, etc (so they should be able to run unattended after starting)
865
- - This means we can get rid of the emails to add trust, as nodes will no longer just "appear", but instead by instructed to appear, at which point we can explicitly ask for their public key and trust it (we should never provide the key, as this is incompatible with a transition to hardware keys).
866
- - We also want the ability to monitor traffic, and make recommendations about having less/more droplets
867
- - These recommendation should display in the UI
868
- - There should be daily or weekly emails about recommendations to some admin email account.
869
- - EVENTUALLY we might have these recommendations automatically followed, for scaling, although maybe only to certain degrees, as automatic scaling can be dangerous.
870
- - AND, it would be nice if we could view/administer MULTIPLE domains at once, OR AT LEAST summarize traffic recommendations for multiple domains at once.
871
-
872
- Automatic digital ocean droplet launching
873
-
874
- History for the purposes of reverting data
875
- - EVEN just backups works, but... storing a transaction log is preferred
876
- - If we store writes, we can re-simulate to a previous point in time. It might be slow, but it is better than losing all your data due to a database drop.
877
- - If we store clobbered data as well, then we will be able to restore even if our history size exceeds the max and needs to be truncated
878
- - Kind of required if we want to use it to store any important data...
879
-
880
- Fix hardcoded email and TOS accept in certAuthority.ts
881
-
882
- AST parsing to detect global side-effects in functions
883
- - Global side-effects are going to break things. We should at least mark root functions that do this, OR, that have any child calls that do this.
884
- - Who knows what libraries we call are going to be doing, they could have all kinds of unintended side effects involving the disk and who knows what else!
885
- - The strictest mode would be the disallow global side-effects unless it is enabled per function call
886
- - ALSO, AST parsing to detect module side-effects (that impact values outside of that module)
887
-
888
- Write proxy replacing
889
- - If we have a lot of local writes it can get slow, due to the proxy. If we replace the writes with function calls it could allow a lot more local writes.
890
-
891
- Schemaed proxy replacing accesses
892
- - Instead of passing an array, find common accesses and replace with a call to a factory function to create a schema, which generates a function, which we then pass the remaining variables to
893
- - Try to put all of these beside the databaseTyped (or at least at a module level!), so we can view the code without source maps and see all the examples of accesses. Their will be 1 for each line that reads from the database (that we can find)
894
- - If we set this schema in the write itself we can greatly reduce write overhead (especially if we have keys that are numbers).
895
- - We can send a binary format to forwardWrites and onValues
896
- - Maybe just for the wire communication. But sending a buffer as the payload, instead of a value.
897
-
898
- Fix multi authority writes
899
- - Multi authority writes are annoying, because it causes issues with predictions. As in, you write, predict your value, but then have to wait because one authority is taking a long time to resolve. THEN, another write uses our prediction, but this time only uses the fast authority. This results in the fast authority rejecting our write, until we can send it the full writes, which may take a while if the slow authority isn't resolving!
900
- - AND, if the slow authority takes a REALLY long time, we might never commit the write, resulting in a valid state, BUT, we can no longer commit any writes! Although, maybe this state is okay, as if we can't write the full write... we probably shouldn't write?
901
- - And... maybe this whole issue is fine, as the authorities will eventually resolve, and then it will be fine...
902
-
903
- === Shippable features complete | TASKS = 12 ===
904
-
905
- Triggers / data pipeline / autorun
906
- - Basically an abstraction of what FunctionRunner does (where it watches the database for values, and then writes other values to the database)
907
- - Setup via a value in the database
908
- - Like a function write, but, this function reruns every time anything it read changes
909
- - Will keep the same thread (maybe a dedicate thread?) for a long period of time
910
- - The ability to watch data, calling a non-committed function when it changes
911
- - The function that is triggered can call synced functions, committing values
912
- - Will be able to sync a lot of state, and also keep values in untracked state (if it wants to)
913
- ALTERNATIVE
914
- - Just use FunctionRunner, and if you need to use dedicated hardware, use another domain
915
-
916
- 1.3) Buffer support in SocketFunction? Hmm... I mean, if we have GB sized files, that would required... And sending a 20GB video file is maybe feasible? Hmm... or... maybe not? Maybe it is more useful for 1MB chunks, which we don't want to have to decode each time? So this is really just "increase data throughput"?
917
- - Essentially, we want a JSON.stringify that converts to a Buffer, and also supports Buffers.
918
- - We will probably eventually support threads, so... maybe we just use Accessors2? Hmm... Or at least some rework of Accessors2?
919
- - Would also need to support it in function argument serialization / deserialization
920
-
921
- Error notification system
922
- - Some kind of immediate notification + email digest system
923
-
924
- Runtime FunctionController type checking
925
- - We should check the type of all arguments against their typescript type by default. That way accidental prototype contamination is less likely. And maybe there are other security vulnerabilities?
926
-
927
- Backups
928
- - Taking our entire database, and backing it up periodically
929
- - We can probably check the time in the last backup, and use that to infer what values will definitely be in it, using this to backup less data (nothing that is already backed up), making this efficient (to write, not necessarily to read)
930
- - Once in a while we would want to write the full state, otherwise after a few thousand backups restoring could take forever.
931
- - We can also be somewhat efficient about reading from the database files, using block create time to know if they are only old and already backed up data.
932
- - At this point we will already have a management interface, so... this should be configurable via that
933
- - We will have to think about how to rate limit it. A lot of databases would probably work by just streaming every single write, then compressing them after they exceed a certain total size.
934
- - AND, when we compress, we probably want to default to the "compress old data more", so we have logarithmic seeking capabilities.
935
- - Might be made redundant by the history navigation feature?
936
-
937
- === Feature complete | TASKS = 3 ===
938
-
939
- Path queries for synchronization
940
- - We could support a way to query paths (likely via a parent selector), to do synchronizations. It makes it slower for the server to continue to send values (each new value required evaluating the selector), but... it could make selecting large amounts of data (millions of values) much faster?
941
-
942
- Local network call forwarding / neighbor prediction
943
- - If there is a write on the local network we can get faster then waiting for it go to the server or back (by local network it mostly means on the same machine, but different process / browser tab / thread).
944
- - We will need to ingest these values as regular predictions, except because we aren't writing them we also need to watch for their valid states
945
- - We also aren't getting if the write is even commited or not (which tells us if it was EVER valid), so... we basically need to assume the value is invalid if (AFTER the valid watch is done), we don't get a valid notification after a short period of time
946
- - We also need to validate writes coming from neighbors, to ensure they are recent enough, and maybe we should do some other checks?
947
- - Having this work for function writes would be nice, but... we REALLY have to trust the neighbor for this to work?
948
- - Well... we could always run the permissions checks? In which case... I guess it would be safe?
949
- - Oh, well.. maybe permissions checks aren't that important. If they are on the same network they are sitting in the same room, so they are somewhat trusted!!!
950
- (And worst case they just cause the data to temporarily change, which isn't the end of the world...)
951
- - AND, we can have neighbor prediction off by default, and only turned on for certain application (maybe on by default in the browser).
952
-
953
- Function call syncing (as opposed to PathValue syncing)
954
- - If we have a physics based system where we predict the physics loop every tick, we can predict a HUGE amount of values (millions), possibly even using the GPU. HOWEVER, the server won't understand our prediction efficiently, and so it will need to send as all the values anyways!
955
- - If instead of syncing the paths we use, we sync the functions that impact these paths, we can have a massive state be kept up to date without saturating the network
956
- - We will have to be told the hash results of these functions, to ensure we are calculating them correctly.
957
- - So, a server instance will have to basically watch all the paths itself, to figure out which functions apply.
958
- - And... presumably the results need to be committed anyways, otherwise this is just a different database! Although... if the function runner and KVP owner are on the same machine, the transfer of paths should be somewhat efficient? And only snapshots need to be saved, so... this could work?
959
- - Basically, you would run the KVP node, and the function runner, on the same machine.
960
- - Some kind of pipeline would trigger the physics function to tick every frame
961
- - This pipeline would be also predicted on the clientside, so it can consistent know about ticks
962
- - Eventually the client will know what functions were run (it should be pretty accurate at knowing this!), And the hash results. The hash results can come later, as the client will know that it will have to simulate old physics results a few times as course changes trickle in.
963
-
964
- The ability to mark writes as "no persistent storage"
965
- - Useful for things such as streaming video, which can then have a pipeline that combined it and stores it in B2 (the pipeline would mark what is stored in B2 and the urls for that content, and the rest of the data would be available on the network directly)
966
- - This makes live streaming possible, but also accessing terabytes of historical data.
967
- - This is different than just video uploading, which should go directly to B2, because it trickles, requiring something to aggregate it, while also requiring instant access.
968
-
969
- Read function triggers
970
- - Allowing setting up a value that calls a function every time it is read, to produce the data (and non client predictable function)
971
- - Allows for exposing an off-network source (like a database) to the network
972
- - By default will just mark everything as valid, BUT, we could add valid ranges
973
- - For databases like mongodb we could roughly do it via changestreams, to warn if the value has changed
974
- - For blockchains we can perfectly detect if the value has changed
975
- - For the writing to external sources side... we would need "unsafe" functions, that don't add any readLocks (so they can never become rejected). Because we can't reject values in external sources!
976
-
977
- FunctionController path syncing
978
- - For small number of reads, the latency of the read is acceptable for the trade off of reduced complexity of FunctionController.
979
- - HOWEVER, for functions with a HUGE amount of reads (GBs of data), we will want to sync it instead...
980
- - We still need to run historical functions, so... we basically need to:
981
- 1) Ask for ALL future writes on a path
982
- 2) Ask for all writes on that path (up to what the authority stores, which is up to and including the last golden value, which should be enough history)
983
- 3) Sync the valid state of all writes we receive, forever
984
- - The hard part, is trying to keep the paths synced simple. If we just sync every access... then we could be syncing tens of millions of paths... which means not only does an authority have to store that much data, it also has to store watchers for all of it... which... will probably be slow?
985
- - Although... maybe not... it is just a Map<string, string>?
986
-
987
- Support arrays, via adding another transparent property
988
- - Basically the object properties, BUT, it will specify it is an array
989
- - The proxy will have to be told of this, and... function properly?
990
- - I think if it's object is [] it will work? Maybe? We should double check how proxys handle arrays...
991
- - Does push just map to a set plus a .length update?
992
- - And what about splices!
993
- - This will be annoying, and probably slow...
994
-
995
- Actually Object.keys() ACID handling
996
- - Ugh... very annoying, and difficult
997
- - A path authority can sync child keys for a range of time (similar to how it syncs a path for a range of time), and then compute a hash of them (sort and sha256). It won't be efficient, but... it should work.
998
- - Will be OPT-IN, because the slowness of hashing the keys is almost never worth the ACID nature of it
999
-
1000
- Object.keyRanges()
1001
- - If we use search patterns that line up with our range sharding, we can efficiently access ranges of keys
1002
- - We will probably need to add more details in our range sharding
1003
- - We might want to specify sharding characteristics via some callback near proxyWatcher.databaseTyped
1004
- - Ex, schema.dataSharding(database => [{ path: database.logs, map: key => +key }]
1005
- - Ex, schema.dataSharding(database => [{ path: database.maps, map: key => key.split("_")[0] }]
1006
-
1007
- Old data cleanup ability
1008
- - Some way to actually delete old data
1009
- - Maybe via both setting all values to undefined, and having nested undefined no longer stored
1010
-
1011
- Ability to selectively undo history
1012
- - Probably recursively by path
1013
- - We would than have to store history under those paths
1014
- - Probably with a limited history (ex, 30 days?)
1015
- - The ability to select changes by values / by time, taking everything in the same changeset (via looking at readLocks), and adding a new change that reverts the change (if the revert value is still the latest)
1016
- - Ex, undo all changes by user for the past day, one specified paths
1017
-
1018
- Disk support
1019
- Local disk support instead of backblaze
1020
- - AKA, implementing backblaze using the local disk of many nodes
1021
- Recent changes disk backup
1022
- - Store changes that are too young to be archived on disk
1023
- - CAN'T use archive the values, as those values are assumed to be valid, so... needs a new format, which is just used to store changes, in case of a crash.
1024
- - If we have a single node, we can stream our changes to the disk, reducing our data loss on crash from ~10 minutes, to < 10 seconds, with very little disk IO
1025
- - We can have a flush time option, which can be set even lower, which will increase IO, but can reduce losses from crash to any point we want.
1026
-
1027
- C++ (.cpp) import support
1028
- - Via WASM compilation, which can be done via https://github.com/sliftist/cpp-portable-loader and `clang-wasm`
1029
-
1030
- C# import support / other language child processes
1031
- - Would only work on the serverside
1032
- - Would have to run a child process which runs the C# code?
1033
- - If we are going to run another process, we can just run any easy scriptable language. Python would be easy to get working as well. On a smaller scale, Java would work too.
1034
- - Ideally we automatically generate typings, but... even if we didn't, that would be fine.
1035
- - C++ would be useful too! As a lot of the time this is useful for making OS calls, which are easiest in C++.
1036
-
1037
- Rust import support
1038
- - Compiling Rust to wasm is even easier than C++, so... we could probably do it fairly easily
1039
- - I'm not sure how to get typings, although they aren't strictly needed
1040
-
1041
- === Advanced optimizations | TASKS = 2 ===
1042
-
1043
- === \/ Speculative tasks \/ ===
1044
-
1045
- - function call results
1046
- - Some way for function calls to return T|undefined, and be given the location they are assigned to, so they can automatically write the result there
1047
- - Is basically the same as adding the path, so... this is probably a good way to do it.
1048
- - Can still with with function predict!
1049
- - Passing the path to the function call is a bit annoying, but... if we return a special value, we can observe where that gets written to in the proxy, so... it isn't so bad...
1050
- - We can even allow writing to multiple output locations
1051
- - Really only makes sense if the function is slow. Otherwise, is should just be an inline function!
1052
- - We should add in function level sharding, so we can exclude function calls from function runners that don't opt into those. That we can split up slow functions from regular functions.
1053
-
1054
- - "limits", to limit resource strain any node can put on PathValueServer
1055
- - limit the total number of paths watched
1056
- - limit the number of writes per second
1057
- - limit the total bytes per second of writes
1058
- - maybe limit onValue callbacks, depending on size, etc
1059
- - Maybe dynamic limits, depending on load
1060
-
1061
-
1062
- - Fix our JSON serialization code converting undefined to null inside of arrays
1063
- - This impacts function calls, which can be really annoying / confusing...
1064
-
1065
- - Actual query subscriptions
1066
- - Tell the querysub server that function we want to run, and everything it watches will be returned
1067
- - Anything the server misses can be additionally subscribed to
1068
- - This allows the cascading latency penalty to be equal to the querysub server latency. WHICH, if it is on the same server as the PathValue server, can be less than 1ms (we will need to turn off some automatic batching to lower it this far)
1069
- - AND, if the querysub server did a full PathValue sync, asking for all values on all paths... the latency would be 0.
1070
- - We would need to be able to fit the entire database into memory, but... if we allowed `watchRecursive(path)`, and either shared Querysub, OR, had queries explicitly map to the path they wanted (which hopefully would either be small, or shared with another server), then... it would just be efficient...
1071
- - AND, the client could even subscribe to `watchRecursive` as well?
1072
-
1073
- Better FunctionRunner startup
1074
- - We should sync all of the repos we will need BEFORE taking any shard space? As the clone could easily take minutes, which would cause functions to timeout.
1075
- - Although... maybe just having it better distributed would solve that problem, as other nodes should automatically pick up the slack?
1076
-
1077
- CORS support, so querysub can be accessed by other domains
1078
- - We would read the CORS values from the database, and they would be set in, or similar to "yarn shard-deploy" / "yarn deploy"
1079
- - Only querysub needs CORS, as the browser should know to only access it
1080
-
1081
- Serverside "local" writes
1082
- - Update code to special case "local" writes, so that we never sanitize remote writes so they never use ReadLocks on "local" writes.
1083
- - BUT, allow "local" writes to have ReadLocks on remote writes (IF we are trusted on that domain). Our system should automatically subscribe to valid states, and maintain the valid state of "local" writes. This prevents our "local" values from having invalid values that stick around (as in, in a cache), even though we might end up writing them to a "remote", which would make them invalid.
1084
-
1085
- Have deploy log the % files and % lines that are NOT allowclient
1086
- - This determines which files we might serve clientside
1087
- - We only really care about files NOT in node_modules, but, in our folder?
1088
- - Although, maybe we should consider any private npm packages as well?
1089
-
1090
- Lazy Function / Function Prioritization
1091
- - If we have functions that can run at any time (but must run in order), then... we can run them later, when the server is more free.
1092
- - AND, we can even go as far as to detect when the predicted outputs of them are needed, running the functions at those times?
1093
- - Maybe we would actually want this to be functions triggered one read, but only triggered once?
1094
-
1095
- Ability to disable syncing of paths
1096
- - Possibly depending on context, such as page
1097
- - Also, possibly just to reduce the update rate for certain paths
1098
- - This fixes the issue of lag (either network, client, or server), when many pages use a commonly changed value
1099
- - Ideally we WOULD like it to be updated, but... if we don't need it updated, and 99% of our server capacity is spent updating a few values we don't care about...
1100
- - We want this to be able to be added post-hoc, potentially live (at a data level, not a code level),
1101
- so the site can be quickly optimized on the fly
1102
- - But of course, the code will also need the ability to specify it doesn't want updates
1103
- - Will basically function as a watchLatestOnce, where it gets the latest value, and then no more.
1104
-
1105
- Better undefined support
1106
- - We COULD make it so for the last run of a function, after all values are synced, we run again returning undefined for every value that is fully synchronized with no child values synchronized.
1107
- - If the first run has every value synchronized we would have to run again.
1108
- Pros
1109
- - This MIGHT make function run more naturally
1110
- Cons
1111
- - Functions are more complex to run (and must run more times)
1112
- Alternatives
1113
- a) Just use atomicObjectRead if you want to read undefined
1114
- b) Use `+x`, `x + ""`, OR `${x}`, to coerce it to a primitive
1115
-
1116
- Support instances in our data?
1117
- - Benefits
1118
- - Instead of passing around string ids everywhere, and having programmers have to know which functions use it, you can pass around a class and just get autocomplete
1119
- - Costs
1120
- - Slow
1121
- - Harder to debug
1122
- - Alternatives
1123
- - Compile time instances => static function + id call
1124
- - Basically, we can "store" and "pass" around an instance, and even "new" it. But
1125
- new => create random id, and store data in static location under that id
1126
- function calls => call static function with instance (which is a string) as a parameter
1127
- instance accesses (including this.) => lookup data from root with static lookup
1128
- - This is harder to do, but... faster, and easy to debug.
1129
- - Function modules with local .databaseTyped call
1130
- - You can go to defintion and see all the exported functions, which are probably how you mutate the data (especially if the .databaseTyped call isn't exported!)
1131
- - BASICALLY, everything starts as a static call. But... those calls may wish to create class instances, and store those (ex, binding data and functions)
1132
- - SO, on seeing a set that is a class (ex, has functions), instead of just stripping the functions, we...
1133
- - Figure out if it is a deployed function (has exposedRootClass decorator).
1134
- - If not, we don't store it
1135
- - Store a new InstanceId in our schema with the data provided
1136
- [DomainName][ClassName].Instances[InstanceId].Data = Data
1137
- - Replace the write with this new instanceId
1138
- - On accessing a value that... looks like an instanceId? We...
1139
- - On reading values we use the Instances.Data path instead
1140
- - On calling functions we load the class from the Source info, and call it
1141
- - Specifically, providing a this context equal to the data value.
1142
-
1143
- Clientside "local" FunctionRunner
1144
- - In theory, some operations will heavily depend on some server function call completely successfully. BUT, also store local state?
1145
- - IF we never need this, then we never need to implement it. But... if we find a case where this is useful, in which just moving the data to the remote is not viable (and there is no alternative)... then... we could do it?
1146
- 1) Allow "local" functions to run using FunctionRunner, so they can automatically rerun when rejected
1147
- - We will require the ability to mount FunctionRunners under the "local" domain explicitly
1148
- 2) Allow (when specified) "local" functions to depend on "remote" writes
1149
- - This requires allowing syncing valid state, etc
1150
- - Without runing FunctionRunner rejections are usually not preferred. But with running it, rejections can result in a rerun, and so are not as bad.
1151
-
1152
- Intercept DB, to allow writes without actual ownership of a domain, cascading to store all values that depend on these changes as well.
1153
- - Maybe only useful for hacking together application, or development, but... seems like it would be pretty cool?
1154
-
1155
- Function call results (for cross domain calls)
1156
- - Probably not a good idea. Maybe... just never do this...
1157
- - Non-cross domain calls are all inline anyways, so this is only for cross domain calls
1158
- Something like: QuerySub.afterFunction(() => OtherController.queueAndRun(), result => TestController.finished(result)), which tells FunctionRunner to do some extra stuff after a cross domain call finishes.
1159
- - We COULD ALSO use async/await. We KNOW when the synchronous part of a function finishes
1160
- - Ex: `let result = await QuerySub.afterFunction(() => OtherController.queueAndRun())`
1161
- - As we are broken in on the machine, we don't need ALWAYS resume (although if the node is killed, and the function needs to be rerun, we would need to re-evaluate the previous call to catch up).
1162
- - We would need know when the await returns, wrapping it with some code?
1163
- - This is... impossible? Although... if the thread is CLEAN, we could just assume the next function will run, resolve, then wait for a promise, then assume the function is finished? It gets REALLY difficult
1164
- - This will break up the ACID nature of the function, which is fairly unfortunate
1165
-
1166
- Size optimization
1167
- - Move register calls into a .client.ts file, so browsers only need to import the interface, and not the implementation too!
1168
- - Better HTTPS endpoint which prebundles some calls, so we can bootstrap faster
1169
-
1170
- Streamlined externals, via a custom site that takes payment and handles setting up all the other services
1171
-
1172
- Lazy connections
1173
- - NetworkState creates a lot of connections, which have very infrequent traffic. Instead of having N network connections, we should allow some connections to be marked as less important, and closed when not used for a bit. Although... if we say had, 10K open TCP connections, is this a problem? Hmm...
1174
-
1175
- Synchronize time ourself
1176
- - We can't trust machines to synchronize their time. Synchronize it ourself in NetworkState (OR, maybe just by using NetworkState), and use that instead of Date.now() everywhere!
1177
- - We'll want to use a waitForTimeToSync() function in places to gate our major code locations to ensure we always have an synchronize time, so we can expose a getTime() function that is synchronous.
1178
-
1179
- Replace ALMOST all external services
1180
- - We can run our own DNS server
1181
- - We can replace firestore with our regular storage controller
1182
-
1183
- Very short expiry times on thread certificates, finding some way to automatically update them while running
1184
- - At first only node <=> node
1185
- - BUT THEN, our proxy should be able to handle updating the peer cert, as it can probably call renegotiate?
1186
- - Our system for the client to update it's cert after the fact can likely be reused to trigger an update of credentials?
1187
- - I assume outstanding connections won't be killed if their certs expire, so... we would actually want to
1188
- add an additional check to close a connection if it's cert isn't updated? Or... we could just call renegotiate, which
1189
- I think serverside TLS connections can do?
1190
-
1191
- Public release?
1192
- Get benchmarks on https://benchant.com/ranking/database-ranking
1
+
2
+
3
+ Trigger values
4
+ 1) validStateComputer.ingestValuesAndValidStates
5
+ 1.5) authorityStorage.ingestValues
6
+ 2) authorityStorage.ingestValues
7
+ 3) lockWatcher2.watchValueLocks
8
+ 4) validStateComputer.computeValidStates
9
+ 5) lockWatcher2.getValuePathWatchers
10
+ 6) pathWatcher.triggerValuesChanged
11
+ 7) pathWatcher.triggerLatestWatcher
12
+ 8) PathValueControllerBase/clientWatcher.localOnValueCallback
13
+
14
+ Source of values
15
+ 1) clientWatcher.setValues
16
+ 2) PathValueCommitter.commitValues
17
+ 3) (predictions) validStateComputer.ingestValuesAndValidStates
18
+ 4) (non-predictions) PathValueControllerBase.createValues
19
+ 5) validStateComputer.ingestValuesAndValidStates
20
+
21
+ Archiving values
22
+ 1) authorityStorage.ingestValues
23
+ 2) pathValueArchives.archiveValues
24
+
25
+ TODO: pathValueAuthority detection
26
+
27
+
28
+ Core parts
29
+ - Valid evaluation
30
+ - Needs external value syncing, which can give it all past values, and all future values for a path
31
+ - Value syncer
32
+ - Needs someone to give it authorities, and tell it when an authority goes down (at which point it finds a new authority and syncs the new values)
33
+ - Authority tracker (supports registering, checking, notifying, and generally synchronizing all authority information between all authorities)
34
+ - No requirements
35
+ - Authority setuper
36
+ - Needs authority tracker
37
+ - Needs value syncer
38
+ - Authority (stitches together functionality)
39
+ - Needs authority tracker
40
+ - Needs authority setuper
41
+ - Needs value syncer
42
+ - Needs valid evaluation
43
+ - Disk value saving/retrieval
44
+
45
+ FunctionRunner is funny. It wants a full history, for reading, but does not calculate the valid states of PathValues.
46
+
47
+
48
+ Optimization / optional features list
49
+ Valid Evaluation
50
+ - Only need to sync some data, not all data.
51
+ - event: sometimes we discard data
52
+ - isTransparent: Sometimes, even though two values are different, they can be considered equivalent.
53
+ - gc: Older values won't change their valid state, so we can discard their locks.
54
+ Value Syncer
55
+ - Path watches
56
+ - Parent path watches
57
+ - Authority path (tree) watches
58
+ - Valid watching (authorities know this, so it's free to also sync this)
59
+ - Only latest/valid state watches (no history, just latest, more efficient)
60
+ - Special connection clobber logic (you can't store the history after you disconnect if you are not the authority)
61
+ - Special logic
62
+ - Reused for internal watches (rendering, etc)
63
+ Authority tracker
64
+ - Immutable authorities, to simplify usages
65
+ - Path requests => authorities might take slices from multiple authorities
66
+ Authority setuper
67
+ - Once on startup, so we don't change authorities, to simplify implementation
68
+ - Leverage disk snapshots to make loading faster and lighter for existing nodes
69
+ Authority
70
+ - The stitching together of all the values is reused by the function runner in order to rerun functions when they are invalidated
71
+ Disk value saving/retrieval
72
+ - Only old values with discarded locks are written, and the history is compressed occasionally
73
+
74
+
75
+
76
+
77
+
78
+ *** Goal is to get to === Full public website stack milestone === ***
79
+ === 3 tasks to fully fledged syncing KVP database, 2023 / 25 / 2 ===
80
+ === 1 tasks to fully fledged syncing KVP database, 2023 / 26 / 2 ===
81
+ === FINISHED fully fledged syncing KVP database, 2023 / 28 / 1 ===
82
+ === 6 tasks to "Full public website stack", 2023 / 31 / 1 ===
83
+ === 5 tasks to "Full public website stack", 2023 / 31 / 1 ===
84
+ === FunctionRunner bones done!, 2023 / 31 / 2 ===
85
+ === Disk garbage collection bones done, 2023 / 11 / 3 ===
86
+ === Access for non-network nodes (via permissions) done, 2023 / 19 / 3 ===
87
+ === Clientside + clientside function prediction done! 2023 / 23 / 3 ===
88
+ === Prototype SD app with 2 creation modes, arbitrary model support, paint, diffuse, openpose, onnx model generation, etc. 2023 / 10 / 1 ===
89
+ === Lots of failed embedding training research. 2023 / 12 / 18 ===
90
+ === Better single source of truth. 2023 / 12 / 31 ===
91
+ === Better serialization format. 2023 / 12 / 31 ===
92
+ === Fixed storage / multi process storage. 2023 / 12 / 31 ===
93
+
94
+ TIMING: Transactions, currently about ~1.5ms per transaction + 10us per write and read path in the transaction
95
+ - About 5ms latency, the fast times are only if you don't wait for transactions to finish (because why would you wait when the whole system is based on predicting writes?)
96
+ - With 1 watcher (with 0 watchers transactions are probably about 0.5ms per)
97
+ - Much of the transaction over is serialization time, mostly in JSON.stringify (to send the value to the watcher)
98
+ - MUST FASTER NOW, if they are batched
99
+
100
+ TIMING: Function calls, about ~1ms-3ms for a trivial function, if it can be batched with similar functions
101
+ - Slower if we need to sync new paths
102
+
103
+
104
+ Fixed function props / function closure parsing
105
+ - Presently, if a parent re-renders, and it passed a lambda to a component, that component will always have changed props. HOWEVER, this is very inefficient!
106
+ 1) Process the file to convert lambdas to provide debug information which includes all of the values they close upon (as properties set on the lambda itself). Also an id, unique to that specific piece of code.
107
+ 2) When evaluating if props changed, if the lambda id is the same, and the closed upone variables are ===, then... it's the same thing, so consider the prop unchanged
108
+ 3) Test if all of our "updateOperation" lambdas in SideOpConfig.tsx work with this. I THINK updateOperation is the same, but if it isn't... we can always recognize nested lambdas.
109
+
110
+ Option to not use permissions checks locks
111
+ 1) Create a way to have any code run without locks (we might already have this?)
112
+ 2) Add a flag in schemas to either specify all functions, or certain functions should run permissions without locks.
113
+ - Also something to specify a function SHOULD run permissions with locks
114
+ - Using the ArchiveViewer verify "heartbeat" goes from 25 to 0 locks
115
+ - If you step into pathValueClientWatcher.ts:setValues in FunctionRunner, you can see the actual paths that are used.
116
+ - Benchmark a simple function to see how much faster this is, and how much less memory we use
117
+ - If it doesn't matter... maybe remove the feature?
118
+
119
+ More corruption resistance files
120
+ - Add a new serialization format, via versioning in the settings
121
+ - Each datum will be length prefixed WITH a special sentinel AND with a checksum for the datum data
122
+ - The sentinel can depend on the length (but otherwise be constant)
123
+ - We can have a recovery mode (automatically attempted if the checksums are wrong?) where we scan the file for length+sentinel pairs (where the sentinel matches the checksum), see which ones have a valid checksum, and then use an algorithm to decide which ones to use
124
+ - Maybe we sort by success rate, which is [-numberOfValidChecksumsOverlapped, length], which is very likely to work EVEN if there are values which have the right checksum, but are actuall corrupted.
125
+ - As long as the checksums are random enough, so... we should never use any small values for checksums, etc.
126
+ - Record the value layout in the settings, so we can generate arbitrary values, even if we've never seen that layout before.
127
+ - A series of parallel object arrays, with each object having flags (which indicate which values exist), values which always exist, etc
128
+ - Value types will be string, float64, byte, Buffer[]
129
+ - and... we might as well add support for short, int, float, and uint (uint is a good way to store a guid, via storing 8 uint variables).
130
+
131
+ Schema/binary PathValues accesses
132
+ PER Path, not per value. Values will likely be lazily deserialized anyways, making them either pointers, or just numbers, stored in unchanging objects, which... should be quite fast.
133
+ 0) First our stream/schema wrappers. Which at first just convert to raw path reads/writes, but once we add schemas they can efficiently create/use those
134
+ 1) PathValue.path (string[]), need to be converted to an opaque type `Path`
135
+ - Helpers to manipulate it, and go back to string[], string, etc.
136
+ 2) Have Path be able to store itself in schema + variables mode
137
+ - Where schema is shared across all Path instances
138
+ { schema: { path: (string|Variable)[] }; variables: (string | number)[]; }
139
+ - Add functions to access the schema and variable values
140
+ 3) Create an efficient `Map<Path, T>`, via using schemas from the paths
141
+ - By requiring Path as the input we can directly use the schema to narrow down the cases, and then within that we just have to lookup values by variables
142
+ 4) Use Path everywhere, replacing PathValue.path, using our efficient lookup to manage it.
143
+ - I think a lookup should handle all the cases. We should be able to nest them as well?
144
+ 5) This SHOULD let us entirely get rid of path joining, which should be SO much faster.
145
+ 6) Update serialization to keep schemas, instead of converting them back to paths. This should be more efficient, and a lot smaller.
146
+ - Values serialization won't change, and we still need to encode the variables, but... it should still be a lot faster.
147
+ 7) Try to remove as many Path conversions to string (and also to a lesser degree string[]), as possible, by making them schema aware.
148
+ 8) Investigate further optimizations
149
+ - Replacing variables with numbers, so we our internals Maps can be replaced with arrays / typed arrays
150
+
151
+ IMPORTANT! Actually... a lot of the following is wrong. We should have this PER atomic value, and perhaps ONLY for paths!
152
+
153
+ Base code
154
+ reader
155
+ let viewTime = 0;
156
+ for (let user of global().users) {
157
+ viewTime = user.viewTime;
158
+ }
159
+ return viewTime;
160
+ writer
161
+ for (let [video, viewTime] of viewedVideos) {
162
+ global().users[userId].viewTime += viewTime;
163
+ }
164
+
165
+ Schema optimization
166
+ writer
167
+ let changeStream = new SchemaPath(() => global().users[wildcard].viewTime);
168
+ write(() => {
169
+ for (let [video, viewTime] of viewedVideos) {
170
+ changeStream.write(userId, viewTime);
171
+ }
172
+ })
173
+ reader
174
+ let schemaWatcher = new SchemaPath(() => global().users[wildcard].viewTime);
175
+ watch(() => {
176
+ let viewTime = 0;
177
+ for (let [value, userId] of schemaWatcher.getValues()) {
178
+ viewTime += value;
179
+ }
180
+ return viewTime;
181
+ });
182
+ reader specific path
183
+ // I guess we have to support gets as well. It should still be faster, as the schema is pre-defined
184
+ let viewTimeWatcher = new SchemaPath(() => global().users[wildcard].viewTime);
185
+ let factorWatcher = new SchemaPath(() => global().users[wildcard].factor);
186
+ watch(() => {
187
+ let viewTime = 0;
188
+ for (let [value, userId] of schemaWatcher.getValues()) {
189
+ let factor = factorWatcher.get(userId);
190
+ viewTime += value * factor;
191
+ }
192
+ return viewTime;
193
+ });
194
+ delta reader (eventually)
195
+ // This can easily be extrapolated to just getting deltas
196
+ let deltaWatcher = new DeltaWatcher(() => global().users[wildcard].viewTime);
197
+ let viewTime = 0;
198
+ onDelta(() => {
199
+ for (let [newValue, prevValue, userId] of deltaWatcher.getChanges()) {
200
+ viewTime += newValue - prevValue;
201
+ }
202
+ return viewTime;
203
+ });
204
+
205
+ - Can be binary, or not.
206
+ - Streams can work with non-streams, and vice versa.
207
+ - The streams need to be setup in a tree, so we can efficiently check for watchers of them
208
+ - We also need to support partial key watching. Often we will watch a few keys (and then within them, maybe all keys at another level).
209
+
210
+ 1) START by supporting write streams (but NOT read schemas), as this allows us to define our schemas.
211
+ IMPORTANT! Actually... a lot of the following is wrong. We should have this PER atomic value, and perhaps ONLY for paths!
212
+ - This will give us a big chunk, which we will pass around (even passing around arrays of chunks).
213
+ - The core will break this apart somewhat, with an object per schema, and then a tree of maps for the dynamicValues inside of it (and global lookups for the locks and values)
214
+ - We will never create this from PathValues, instead, we will append values to specific schemas as we build it
215
+ - And... locks need to be kept track of as well
216
+ - So we need a global "captureWrites", to set the state
217
+ - It will return a function, which will take parameter to finish the writes?
218
+ - Or... something. We need to look at proxyWatcher and see what the best way to do this is. I think it might check the values before finishing them?
219
+ - The schema builder functions (on the object returned by defining the schema), will then internally add to a lookup in the globally capturing state (keys by the schema seqNum)
220
+ - Directly adding to the PathValue for that schema
221
+ - A lot of fields will only be set on finish
222
+ - I think we still need to support the helper flags (.valid). Which is actually fine, even if it's binary. It's fairly easy to flip flags in binary data...
223
+ - When we need to reason about PathValues independently we can provide some kind of pointer than can be used in conjuction with the chunk
224
+ - Although most of the time we will just provide iterators to iterate over all the PathValues?
225
+ - I guess eventually the pointer could be serializable too, so that we could select PathValues with a Chunk + Buffer containing pointers, so we entirely manage our own memory. But... probably not for a while, as { schemaId: number; index: number }[] should be VERY efficient to allocate and store, especially if it isn't persisted.
226
+ // PathValue
227
+ {
228
+ schema: ({
229
+ type: "constant";
230
+ key: string | number;
231
+ } | {
232
+ type: "dynamic";
233
+ })[];
234
+ values: {
235
+ dynamicValues: (string | number)[];
236
+ // Pointer to position in value Buffer
237
+ setValue: Pointer;
238
+ // SeqNum to ReadLockGroup (as often many values will have the same set of ReadLocks)
239
+ readLocks: Pointer;
240
+ ...more fields for various PathValue fields
241
+ // NOTE: ReadLocks need to reference another binary structure
242
+ // - Probably via a locally unique seqNum, which is remapped upon receiving data over the network
243
+ }[];
244
+ }[]
245
+ // ReadLockGroups
246
+ {
247
+ seqNum: number;
248
+ lockSeqNums: {
249
+ schemaSeqNum: number;
250
+ seqNum: number;
251
+ }[];
252
+ }[]
253
+ // ReadLocks
254
+ {
255
+ seqNum: number;
256
+ schema: ({
257
+ type: "constant";
258
+ key: string | number;
259
+ } | {
260
+ type: "dynamic";
261
+ })[];
262
+ locks: {
263
+ seqNum: number;
264
+ dynamicValues: (string | number)[];
265
+ startTime, endTime, flags, ...;
266
+ }[];
267
+ }[]
268
+ // Values
269
+ {
270
+ seqNum: number;
271
+ // arbitrary binary data
272
+ }[]
273
+ 2) Isolate this object structure, so it is only manipulated or read via helpers, so we can change it to use a binary format later on
274
+ - This means adding functions to go to/from binary, even at the start, and just having it go to PathValue and use PathValueSerializer for now.
275
+ - We will have unique id (a number, locally unique) per schema, so we can very quickly tell if anything might match
276
+ - OH! use global objects for schema defs, so equivalent schema defs share an object. This lets us store flags for them!
277
+ - Not the whole schema, just the def part
278
+ - Just a pointer as well
279
+ - If we have conflicts with other schemas (now or in the future), this needs to point to the shared data for the combined schema.
280
+ - Expose the shared objects as createdSharedAccessor<Object>(key) => { get(schema); set(schema, value) }
281
+ 3) THEN, use this structure throughout, replacing PathValues in the entire application with lists of these chunks
282
+ - We'll probably store in the core in an entirely splayed manner. Maybe the schema, then a tree of dynamic values with the leaves being the set value?
283
+ - The set value will contain much of the PathValue, excluding the path
284
+ - Update the core code to store data in schemas
285
+ - If schemas conflict, we take the more general union of them. Annoying, but this should be rare, as schemas aren't recursive.
286
+ - Ex, "x.*.z" and "x.y.*" NEED to be combined to "x.*.*", at least for storage.
287
+ - AND, we need to store the original schemas, and have them MAP to the combined schema!
288
+ - So we end up with a non-schema data storage, and a bunch of schemas
289
+ - TWO ways to access data in schemas
290
+ - If you have a schema, you just directly lookup your data in the shared schema data, which is owned by the core, but every schema points to it
291
+ - If you don't have a schema...
292
+ - We need a lookup of schemas as values with wildcard (I guess empty keys equals a wildcard? Hmm...)
293
+ - I think we disallow reading empty keys? If not... maybe we should? They are already used a wildcard watches anyways...
294
+ - Check each part of the path, with wildcard filling the rest "x.y.z" => ["*.*.*", "x.*.*", "x.y.*", "x.y.z"]
295
+ - OH! That's not enough. Because... "x.*.z" is a valid path, which won't match any of them.
296
+ - So... maybe a sorted list?
297
+ - Should be reasonably fast...|
298
+ 4) Then update it to be binary
299
+ - On write
300
+ - And on network traversal
301
+ - I think PathValueSerializer needs to be rewritten. Basically, it will be given schemas ahead of time, sometimes (or maybe always, as we might as well always encode in our base schema), and then encode the paths more efficiently.
302
+ - OH! ReadLocks need some kind of schema as well. Hmm... I guess, they can use the schema of their readers?
303
+ 5) ALSO, create some kind of global string lookup? A lot of string keys will be the same, and it's easier to manipulate/store numbers
304
+ - We will need to provide definition when we send them over the wire though
305
+ - THIS is IMPORTANT! Without this our strings become inefficient, and the fastest code would involve mapping all strings (such as userIds), to numbers at an application level, which is a lot of work that the framework should really handle...
306
+ - Maybe not for ALL string keys? Hmm...
307
+ - OH! And... they only need to be unique per schema? Hmm...
308
+
309
+ - Test with structures like "components.*.x" (but lots of different x values)
310
+ - Hmm... the big speed issue is that the component ids don't map to an array. Although, in theory, we could do that, remapping ids to indexes. And then... reuse indexes on gc, so they become direct offsets into binary memory.
311
+ - Then... I guess we can remember the last mapping, and so quickly go from string to index, with just a === on the strings (which should just be a pointer comparison), which... and even though the index is dynamic, that's still basically just a pointer. So... we could recover a lot of the speed
312
+ - Maybe we should try this out in a mini-benchmark, with a greatly simplified structure (values constant size, no extra fields, all schemas are just 3 long and have similar structure)?
313
+ - AH! Have the schema context wrapper (so schema reads/write get registered), give us a context object, which it gives back for the same type of access.
314
+ - THEN, we can have state per watcher, and... maybe per watcher+schema. Then we can use this to cache the last keys at indexes per schema, per watcher... and for component accesses... this will always be the same, making the mapping instant every time
315
+ 6) Support reading from schemas as well
316
+ - If the data is already stored in the schema format, querying in this format should be very fast
317
+ - We will need a shorter read binary format, which has a schema and dynamic values
318
+ 7) AUTOMATIC schema definition generation from createSchema (replacing the code), and replacing (where possible) the uses of the schema to use the schema definition
319
+ - Probably the hardest part, but... the most important. Without this our application code becomes too difficult to use
320
+ - Nested accesses become difficult, but... not impossible.
321
+ - Object assignment needs to become global
322
+ Local assignment
323
+ let list = data().list;
324
+ for (let datum of newData) {
325
+ list[datum.key] = datum.value;
326
+ }
327
+ Global assigment
328
+ let list = data().list;
329
+ for (let datum of newData) {
330
+ data().list[datum.key] = datum.value;
331
+ }
332
+ Schema based
333
+ let list = data().list;
334
+ for (let datum of newData) {
335
+ listSchema.write(datum.key, datum.value);
336
+ }
337
+
338
+ Reads are more likely to use highly nested assignment... even cross function
339
+ - Cross function stuff is hard.
340
+ - We might need to specialize functions?
341
+ - As in, per type of possible schema input object, make a new function, and still leave a non-schema function.
342
+ - Actually... this should be fairly easy. We aren't going to use === on the functions... ever? And if we do... then I guess it'll fail (or they can mark them as non-specializable, or... we can detect if === is ever used on the function).
343
+
344
+ 8) Delta watchers
345
+ - If we store change reasons in the schema format... then we can query deltas for free
346
+ - Stored in the packed format.
347
+
348
+ Global nested object state for latest values
349
+ - Can have multiple global objects, maybe one per collection / schema?
350
+ - We still need to register reads
351
+ - If we can get code to not write duplicate reads (which a lot of code can be verified to not do anyways), then tracking the reads becomes very simple with our binary schema data, basically just involving writing numbers (or even just the start/end of ranges) to an output Buffer.
352
+
353
+ Code transformation for accesses
354
+ - Add code which can parse a function, find any schema accesses (this requires walking the type tree, and maybe checking other files), and transform the code to call data.q() functions instead (if the schema is a schema2 schema)
355
+ - Add support for __callerFileName__, etc so this function can determine the caller file, so it can know how to parse it.
356
+ - At first do this via a stand-alone function call?
357
+ - And then make it a parameter on the schema?
358
+ - OR, should we transform the code directly?
359
+ - If we do it at runtime, we have to figure out capturing scope again. This is hard, and breaks static variables, but... maybe that's okay?
360
+ - We could always warn about static variables
361
+ - For any non-static variables (constants, functions, etc), we will need to duplicate them, and anything they access, etc).
362
+ - THIS is actually REALLY nice. We can add a file name for this so it can be discoverable in devtools, or, the user can set `debugger;`, at which point, they will see a file which just has the function and anything it depends on. This makes debugging easier, as it removes everything not involved in that function (and more importantly, shows everything that IS involved, which could be surprising, as you might miss the fact that certain functions are being called, but if their definition is there you are far less likely to miss it).
363
+
364
+ Code transformation to turn types into schemas?
365
+ - If we could do this... we could get fast behavior from normal typescript code
366
+ - We would need an out in case the code accesses the schema in a non-schema type way (just for accesses, the backend should already be able to handle this).
367
+
368
+ Binary ValuePaths, which are never decoded
369
+ 1) Allowing accessing a value in a way which returns the intermediate deserialized object (the Buffer[], strings lookup, etc) PLUS the id (or an object?).
370
+ - Call it, ValuePathBinary
371
+ 2) Allow using ValuePathBinary as an input to SchemaPath parameters, updating the SchemaPath code to directly copy the binary data
372
+ 3) Create a function which exclusively accesses ValuePathBinary, using it for nested sets (getting a value, using that to index another value, etc)
373
+ - Verify we are never actually decoding the values
374
+ 4) Hack together something where we can NOT decode strings in the ValuePath serializer, sometimes, as our special function will never use strings directly.
375
+ 5) Benchmark to see the benefit (probably via memory pressure), or not decoding the strings?
376
+
377
+ Automatic commit wrapping of local synced writes
378
+ - If we write or read from local synced state... and are not in a transaction, we should start a transaction, which exists behind the end of the call, and is only stopped:
379
+ a) After Promise().finally()
380
+ b) When any non-local synced state is accessed (which will cause us to throw as well)
381
+ c) When any transactions are explicitly started
382
+ - This resolves the issue of having to constantly wrap state writes, which is just annoying
383
+ - We SHOULD wrap this into a singleton local watcher which we re-use for local events ("once" watchers). This will make events faster, and should make it easier to implement.
384
+ ALSO
385
+ - Allow accessing local state values without being in a synced state?
386
+
387
+ Server crash log protection
388
+ - Up to 10 times per hour, synchronously write unclassified logs to special single log blocks
389
+ - We will delete these special blocks when we finally write the logs
390
+ - This way if we get a fatal error, the console.error before we crash (which HOPEFULLY) we get, will let us know what went wrong.
391
+ - OR... maybe not... maybe the server manager should just watch for crashes and log the most recent stdout/stderr logs?
392
+
393
+ PathValueController direct remote database support
394
+ --ext-remote "D:/code/other/loader.ts"
395
+ {
396
+ /** undefined means it doesn't need to be loaded remotely */
397
+ loadRemoteValue(path): Promise<unknown | undefined> | undefined;
398
+ }
399
+ We'll probably have a timeout, just providing epochValues if loadRemoteValue takes more than a few seconds
400
+ We COULD allow for core to be resolved to the same thing independent of version (by making the core register itself globally, and all cores to use the same thing).
401
+ - AND, we could do the same thing for a few other files, such as clientWatcher, etc
402
+ - This would allow clientWatcher.setValues to be called explicitly
403
+ - This MIGHT be useful, but... then again... it adds a lot of complexity
404
+
405
+ Hot render throttle
406
+ - We want all inputs to be hot, BUT, to not render on each keydown. So... we need to delay rendering triggering?
407
+ - BUT, we run into issues if a button then runs, which expects to have the most recent data closed upon? Ugh...
408
+
409
+ Auto commit mode
410
+ - If anything is accessed outside of synced state, enter a commit, and end that commit when:
411
+ a) Promise.resolve().finally
412
+ b) We try to end any other commit
413
+ - Should be a Querysub config value which is on by default
414
+ - VERY useful clientside
415
+ - We need to support "lazyClose" watcher in proxyWatcher, that DOESN'T reset the runningWatcher UNTIL it is explicitly reset, OR, until another watcher tries to run
416
+ - We need to detect synchronized accesses in proxyWatcher and start this special type of commit
417
+ - We need to know if we are in this state, and THROW if we access any non-synced paths
418
+ - This is mostly for non-local paths
419
+ - Throwing WHEN they paths are accesses is required, otherwise the error location is too annoying
420
+
421
+ Watcher diff mode
422
+ - Running stress tests (satTest.ts) with a single watcher shows that the slowest part is our function watcher, specifically all of the parts that don't operate in a delta mode (ex, setWatches).
423
+ - If we made a watcher fully support a delta mode (which is fairly easy, as it converts all the changes to deltas anyways), the watcher would likely work MUCH faster.
424
+ - ClientWatcher needs to expose a delta interface
425
+ - proxyWatcher also needs a delta mode
426
+ - Rerun the benchmark after using the for FunctionRunner, and our call/s should go up significantly
427
+
428
+ Shard display / manual sharding / automatic sharding
429
+ - Sharding will be fixed on process start
430
+ - We can augment our database path size distribution display to also support explicit sharding control
431
+ - This means we have all machines register, and then tell them what pathAuthorities to run (generally just running a single process, but always a new process, with us never changing pathAuthorities for a process/node)
432
+ - THEN, we can write code that automatically sets the sharding configuration, not changing it too often, but keeping everything fairly well distributed
433
+ - Our merging should automatically handle cleaning up dead nodes, so we really just need to create and kill processes to control sharding.
434
+
435
+ Function implementation benchmarking / profiling / diagnostics
436
+ - Have a debug page which shows all functions that have been run, with diagnostics
437
+ - Time taken (total, average, etc)
438
+ - Lock count
439
+ - Rerun count
440
+ - Really important, as it is easy to right functions with a high rerun count. Ideally every function should run at most 2 times, once to know which path it requires, and again once those are all synced.
441
+ - Reject %
442
+
443
+ Fix wildcard permissions
444
+ - Wildcard values should do more than just check "", they should check all possible direct permission keys
445
+ - This fixes the `{ admin: { PERMISSIONS(){ return users()[config.callerId].isAdmin; } } }` type of permission check
446
+ - Also `{ serviceSecrets: { PERMISSIONS(){ return false; } } }`
447
+ - It isn't SO bad, as Object.values() only provides shallow values, but... we should still fix it.
448
+
449
+ Browser local PathValue caching
450
+ - https://rxdb.info/rx-storage-opfs.html
451
+ - Do it at a core level
452
+ - Start with being able to enable it for specific paths, triggered by a flag to createLocalSchema which causes that entire schema to be synced.
453
+ - Or... createLocalSyncedSchema?
454
+ - Eventually add support for remote paths, but in a way to not erronously trigger the sync flag
455
+ - We need to batch the PathValues that we store, and load them in efficiently, etc, etc
456
+
457
+ Combined console/event display
458
+ - Everything grouped by event, with minor sorting based on the latest events, but it is mostly just an aggregator
459
+ - Show the most recent events
460
+ - Allow drilling into a specific event, searching it, filtering it, etc
461
+ - Allow setting up debug workflows
462
+ - Take a specific event / search, run some code on it to filter and create another search, which gets values, which we then can run another set of code on, etc, cascading
463
+ - Tree summarization / navigation
464
+ - For tree display, take any node that has > N children (maybe 100?), and wildcard it, collapsing all children into a single node (and merging all child tree, etc, basically rewriting it so instead of many keys, it has exactly 1 key). This should give us a small number of finite paths. Then we can drill down far enough to split each node into large enough chunks (so we aren’t only looking at roots, but also not only looking at children), with the split factor being configurable (so we CAN just look at root, or just look really high level).
465
+
466
+ Heap analyzer
467
+ - Start by showing shallow size with a depth of 1, and then allow clicking to add more depth
468
+ - Maybe allow viewing the options at each level (instead of just taking the first reference)?
469
+
470
+ Better serverside logging
471
+ - Batch it or something, so 20 requests per second (which isn't even that much), doesn't cause the console to be unusable.
472
+ - Maybe... group by type, and show all the categories on the screen at once, allowing the user to type into the console to expand a group, pin a group, filter, etc
473
+ - The most recent groups can be shown first, WITH, extra prioritization for severity
474
+ - Expanding a group will show the specific entries (otherwise we just show the a count, and MAYBE the latest value?)
475
+ - Filtering would probably be useful too
476
+ - OR... we could just show a really simple log, and link to a web UI?
477
+ - In the web UI we could show logs for multiple processes too!
478
+ - We could tag groups with processes that contribute to them
479
+ - And also show active processes, and recently terminated processes
480
+
481
+ Cloudflare bootstrap caching
482
+ - Add round robin DNS entries (or just verify we are already using it), for our root domain, and turn on some level of caching
483
+ - I think we return the correct e-tag, so we should be able to cache fairly heavily
484
+ - If we turn the file download to be 2 stage we can cache the second stage forever (including the hashes we obtained from the first stage), which... SHOULD be a lot faster (the first call will be slower, and two calls are required, but the second call should be always cached in cloudflare, and then in the user's browser).
485
+ - Fix clientside node routing
486
+ - Update our certificate generation to have one level of wildcard.
487
+ - I believe (we should verify this), you can't have multiple levels of wildcards, so... this certificate WON'T be able to impersonate our full node domains (which are 3 parts), and even if it can... we do extra verification of the public key, so... it would actually be fine.
488
+ - Publish A records for our machines
489
+ - Convert the node ids to just use the machine id clientside, allowing the client to pick any Querysub node
490
+ - Have the node list accessed via HTTP, and cached for a few minutes, as it will change constantly, but... clients don't need the latest version
491
+ - I GUESS after the first read, if we can't find a Querysub node, we can add some flag to disable caching for it, which would eliminate the lag between fixing all the servers and the site being usable again
492
+
493
+ Querysub node sharding
494
+ - Presently we allow any client to use any Querysub node. We might want to shard them too? Or at least make it allowed?
495
+
496
+ Archives disk caching
497
+ - Cache backblaze files on disk.
498
+ - Only files that are at the file size limit and old enough (coordinate with the thresholds the merger uses).
499
+ - At a certain point the merger will stop touching files, or touch them less often, and so we will be able to cache them on our disk for a while.
500
+ - Limit our size to a fixed disk % (probably 5%?), with a configurable list of disks to use
501
+
502
+ Clientside sourcemaps in error callstacks (maybe just have Querysub install the error stack library by default, in the browser?)
503
+
504
+ typenode improvements
505
+ - Log typenode timings after setImmediate, top 10 + remaining timing, in a table
506
+ - Also, maybe entries with time > 10ms (including remaining), and not logging the table if this would make it empty.
507
+
508
+ Tool to suggest balanced path ranges (based on data size, not access patterns).
509
+ - We can even just have it be a function, which we call on startup (telling it the count of nodes, and which index we are), and which caches the shards. It isn't live automatic sharding, but it is technically offline automatic sharding, which isn't too bad.
510
+
511
+ FunctionRunner automatic balancing
512
+ - Exposed functions will also define define their shard distribution.
513
+ - Ex, setShardDistribution(addToUserBalance, (userId, balance) => userId)
514
+ - It is somewhat important for the shard distribution to be consistent across various functions.
515
+ - If this is just set on our most commonly called function, sharding should work quite well
516
+ - IF a shard becomes too far behind, other nodes will pick up the slack, with the closest shards picking it up first, then those farther away, etc, etc.
517
+ - Hopefully we can use the shard values to prevent too much duplicate work, even without any direct communication between the nodes
518
+
519
+ Turn on and test backblaze storage
520
+ - Create a utility to go from disk => backblaze (and might as well go from backblaze => disk)
521
+ - Test starting a server on another machine
522
+ - After setting up the .json keys files, it... should just work?
523
+
524
+ Anti-rejection code (isn't REQUIRED to make the database useful, so we should wait. Would be pretty slick though... Ideally this can all be extensions that have no or only modular impact on the core functions, or even no or modular impact on the proxy? Although that might not be possible...)
525
+ Summary
526
+ Excess work (N^2) due to rejections
527
+ - Past time reading (removes causality guarantee, almost completely preventing rejections)
528
+ - Unsafe reads (to remove locks, and then selectively add them back)
529
+ Jitter (due to rejections)
530
+ - Pase time reading (even further in the past than required, to allow a buffer of values)
531
+ Jitter (due to varying client lag)
532
+ - Past time reading
533
+ Lag makes player near impossible to kill
534
+ - Adjust writeTime to stay > min value, depending on the values changed (some values have to be < 100ms, etc)
535
+ Laggy player does things only they can see
536
+ - Adjust age of past dependencies to stay > min value, depending on the values changed (some values have to be < 100ms, etc)
537
+ Clientside operation lag resulting in inability to perform delicate operations (ex, shots miss)
538
+ - Past time reading
539
+ Clientside operations applied out of order
540
+ - More consistently adjust writeTimes (instead of one some functions but not others)
541
+ Operation cause-and-effect no ordered (phasing through objects)
542
+ - Stop using past time reading
543
+ Clientside hacking script to automatically react to values in the past
544
+ - Serverside changing of time, rejecting clientside predictions, AND, something to tween to the correct state, to prevent snapping
545
+
546
+ todonext
547
+ brainstorm: `So what is the FAIREST way to prevent lag from HELPING.`
548
+
549
+ == Test app, with top down "shooter"? (with just circles and lines?)
550
+ - Movement will be "key" based, not frame based. This is harder to implement, but takes a lot of pressure off of the synchronization code.
551
+ - And... the look position will be... just blurred, using maybe 3 ticks per second? Ugh... that will work, for now. Eventually the synchronization code SHOULD be fast enough to handle 30 ticks per second, but presently... our overhead is just too high, and so operations that SHOULD take ~10ns take ~10us, and there's not much we can do about it until we use deltas everywhere, and replace our usage of proxies.
552
+ - This makes the physics system harder to calculate. Although, not that much harder, as we can still use the "intersection" approach, if we want to, by locally emitting positions per frame.
553
+ - I'm not sure the best way to do it... intersections (which really decomposes into line segments) is nice, as it allows infinite precision.
554
+ - Of course, if we have any concept of "gravity", then it is harder. BUT, even then, we can still extrapolate, and even better, we can extrapolate with non-linear segments, which is really the best way to do those kind of simulations anyways.
555
+ - We WOULD require a way to make the non-linear segments update consistently, but... as we have a universal time, that wouldn't be so hard...
556
+ - WELL, no matter what we need some kind of snapshots, to prevent having to re-simulate TOO much state.
557
+ - So... we can have "keys" only last for a certain period of time, and after that, movement stops. Of course, if we had gravity this would make it possible to freeze in mid-air, but, eh... it is what it is.
558
+ - AND, if we had any NPCs or AI, those would require an app server, or some kind of general purpose interval function calling server anyways...
559
+ == Add AI to automatically move around nodes, and automatically shoot
560
+ == Add multiple nodes, with various latencies, which flucuate with different magititudes
561
+ - We should see nodes jumping around
562
+ - It will be hard to hit laggy nodes, as you don't know where they are!
563
+
564
+ Delayed prediction rejection
565
+ - Not sure if this is needed, but in theory if there is contention a function might always require multiple runs to be correct. This means our prediction will always be rejected. BUT, it is probably pretty close, so... we should just keep it around for a bit, until FunctionRunner finishes up the function, giving us the most up to date state.
566
+
567
+ Past time client reading
568
+ - Intentionally read client positions older that the latest, in order to cause a smoother state.
569
+ - Will read a combination of a fraction + value in the past (so clients that are 1000ms +/- 500ms
570
+ still look like they are smooth)
571
+ - The readLocks will have to have an empty time range (as we KNOW their range will have invalid values)
572
+ - At least for write values. For the UI, this doesn't matter, the UI is readonly
573
+ - Bakes in these values to the function call when asking the server to run the function
574
+ - When we evaluate the function call, if those paths are read, we have to adjust the read times
575
+ - OF COURSE, only if those paths are read with the request for "past time client reading"
576
+ - Allow CUSTOM limiting (we always limit to a few minutes), of time in the past, adjusting the read to be more recent if it is too old (by the Querysub, on received time)
577
+ - This is important in competitive games to prevent things from undoing a lot of other state, just because one client is lagging badly.
578
+ - TWO settings, one on a global level, and another on a path level, inside of FunctionRunner (using the writeTime as the base)
579
+ - MAYBE we actually limit read times based on the writeTime, which we can use to estimate the lag. Ex, they can only read back as far as 4X their current write lag? The default won't be to do this check, but... for competitive situations we do want to do it...
580
+ - The writeTime will still be the client writeTime, so by default we preserve client side-effect ordering (ex, cast "shield", then "fire", so the shield protects against the fire)
581
+
582
+ ++ Fixes nodes jumping around, as well as making it hard to hit laggy nodes
583
+
584
+ == Add a check so that dead nodes can't shoot anyone (which we might already have as check?)
585
+ == Laggy clients will be hard to kill, because by the time they know they are dead, they will have killed their attacker in the past, so they won't be dead anymore!
586
+
587
+ Ignored client write times?
588
+ - With support of "past time reads", we can always change the writeTime (somewhat, we will have to add a special case where all reads are now forced to be past time), without causing a rejection.
589
+ - This would need to be down on a function level (instead of on a path value)
590
+ - This would allowing resurrecting bad values, as well as changing the order of effects (ex, shield, then cast fire. BUT, if shield is run at server write time, it could happen after the fire, and so the fire could affect you even though you KNOW the order was correct).
591
+ - Which... is fine, we just have to make it clear that this reorders the function implications
592
+ - ALSO, make it clear that this can cause REALLY bad rejections. So if this is done with movement... the client better have really good rejection handling, or it will jitter like crazy
593
+ - ALSO, if the time of our write is important (ex, if we store keys, and then your position is your timeDelta * velocity), then this breaks that (so it isn't just order sensitive, but time sensitive values it impacts)
594
+ Expose writeTime to functions
595
+ - For example, so they can give some leeway in deaths:
596
+ `if (isDead && isDead < writeTime - 50ms) return "deadCantShoot"`
597
+
598
+ ++ Fixes making it hard to kill laggy nodes (by making them shoot at the serverTime, not the clientTime, so they can't surprise clients with "you're dead")
599
+
600
+ == Add "gravity" power, which either accelerates all users towards or away from the user
601
+ == ACTUALLY, I think the problem will be drift. If we are constantly writing, and assuming our prediction are correct, and having them read the previous predictions, we can get really far off from the server
602
+
603
+ Client cascading call resimulation
604
+ - To prevent drift, we can rerun client calls after a rejection. This is a bit annoying, and requires some way to hook directly into rejections (which... actually isn't that hard?)
605
+ - Basically, instead of clobbering all ReadLocks on call predictions, we identify any ReadLocks on other calls, and then watch them explicitly for rejections. If they reject, we re-simulate our call, writing in such a way that we force our previous call to be rejected (which then triggers ourself for future calls, etc, etc).
606
+ - We might need to write to versions MUCH farther in the past, as now we need to increment the version, so... yeah...
607
+ - ONLY if we don't have our result, of course, otherwise we would have no reason to run...
608
+ - Probably do this in a batch, which considers all calls after rejections, then deep compares values, then follows the call chain to get all candidates for rerunning (and the order they should rerun)
609
+ - We then need to wrote the original PathValues per result, which... is fine.
610
+ - ALWAYS update all the way to the latest, OR, doesn't update.
611
+ - Add a kind of throttling so we don't always update to the latest, to prevent lag causing huge amounts of cpu work. This will cause large "snaps" when we do resimulate, but... it will prevent the browser from locking up for too long?
612
+ - Set a constant factor (maybe... 60? as 60 * 16 is close to 1000?), of our extra call rate. Then it is relatively easy to throttle, basically, every resolved call value (they are all rejections) adds +60, and every rerun (each value different causes reruns, but can cause many, if there are multiple calls that depend on it) takes away 1 per call.
613
+ -Make sure to measure this time too, as it will be interesting to see how much time it takes
614
+
615
+ ++ Fixes drift
616
+
617
+ == Will there be a "jump" when a user stops toggling their power? If we are only looking at their keystrokes in the past, our predictions will always be off, but it should be relatively smooth?
618
+
619
+ == Add collisions
620
+ == After having very few users this becomes intractable, due to the inefficiencies of calculating collisions
621
+
622
+ PHYSICS HELPER
623
+ - Takes many path+shape+times as input to seed the data
624
+ - Expose a function which takes a shape, and a range of times (as we don't know which path we are reading, so we don't know which past time to use), and gives all paths which might be close to it
625
+ - We then take the paths that MIGHT intersect, and do full collision checks
626
+ - We can do it with "past time" reads or not
627
+ - "past time" reads allows object to phase through each other
628
+ - regular reads causes more rejections, which can cause more server load, and clientside jitter
629
+ - We should make it so one half of the world does regular reads, and the other does "past reads", and see the differences
630
+ - Well, the difference should be, the "past time" world has no backtracking, so if you do a jump puzzle on your end, you did it. BUT, the "past time" world will also have more objects phasing through each other, and you will see other clients jumping on invisible platforms, etc, etc.
631
+ - The "regular time" world will be more consistent, but sometimes you will swear you did something (jumping on a platform), but then you will backtrack and fall through it, because it WAS broken before you could jump on it.
632
+ *** Implement Local cache helper ***
633
+ - If we create a watchFunction with proxyWatcher, we can make the output synchronously accessible to other functions, as a way to allow unsafe reads
634
+ - If anything accesses this AND the accessed watcher has never been fully up to date (if it has been, but now isn't... allow it, I guess?), then the accessor is not fully up to date as well. It doesn't tracks it as a dependency, but instead as a "watch"
635
+ - AND, when the cache is up to date, the watcher will rerun
636
+ - AND, if the server does the same thing, we will USUALLY result in similar values, BUT, when we don't the server only runs the function once, so it will decide the final result.
637
+ - For things that matter, such as shooting a player, we will want actually dependencies, but this can be used to limit the things we access which we have to depend on
638
+ - Can lazily run, and just uses the database, so it will run in isolation fairly easily
639
+ - When accessing the result, sometimes... register the accessed values as being used (so they aren't unsynced). This might be annoying, and require some batching / delay, but... it is very important!
640
+
641
+ ++ Makes system a lot faster, by reducing rejections that don't change the value, and by increasing the efficiency of comparisons
642
+
643
+ Automatic large value storage separation
644
+ - If a value is > ~10MB, when we write it to archives, we should ALWAYS break it into another file
645
+ - Have this decided via a flag on the PathValue, which can be dynamically set via size, or explicitly set.
646
+ - We should lazily read these values
647
+ - Garbage collection them after a while, when the file referencing them disappears
648
+ - We should create a cache of these on disk as well, so even if we do need to read the values, we can often just risk off of the local disk
649
+ - This will be annoying, but... if we already have disk value offloading, it shouldn't be so bad... AND, could really reduce startup time
650
+
651
+ Archives disk cache value separation
652
+ - If we already support large value separation, we can also separate values from archive value disk cache
653
+ - STILL via some limit
654
+ - Because it is on our own disk, we can seek inside the files, reading JUST the part we want, so the limit can be much lower, probably 1KB?
655
+ - This can reduce startup time even further
656
+
657
+ Network visualization
658
+ - NONE of this will use our PathValue system, and will instead use specialized instrumentation functions
659
+ - Will still be realtime though, and show paths, etc
660
+ - The nodes, and paths, in the browser, with information on time alive, etc
661
+ - Traffic information
662
+
663
+ Multi domain support
664
+ NOTE: Special case LOCAL_DOMAIN, so that we: 1) don't trust anyone else's LOCAL_DOMAIN, and 2) assume no one else trusts our LOCAL_DOMAIN
665
+ - If you call a function on a cross domain, it won't execute inline, but instead result in a write
666
+ - rootCertDomain needs to be removed and updated with a dynamic domain
667
+ - NetworkTrustController needs to maintain trust per domain
668
+ - Only allow readLocks on paths in trusted domains (otherwise throw, not even committing the write).
669
+ - Writes to domains that don't trust us readLocks sanitized, so they don't depend on any of our domain values, otherwise our writes will become rejected immediately
670
+ - Ensure we are still ignoring writes from nodes that aren't the authority on them (I think we are, but this becomes even more important, as now we might partially trust a node, accepting some values, but not others)?
671
+
672
+ Querysub bootstrapper + git repo file server + local file system server
673
+ - Basically... instead of requiring a static file server, we can have Querysub automatically host the files / repos based on some data written during deploy (probably specified via a function that is called in something the deploy.ts imports.)
674
+ - Querysub won't even know which servers it is hosting, and just host a generic bootstrapper, which when run on the client will just do a regular data access with the domain + path to get the entry point
675
+ - Querysub will basically just have a utility to go from gitrepo => file WITH, load development file system support.
676
+
677
+
678
+ Automatic schema usage/generation
679
+ - Parse the types to generate the schema, then automatically update accesses to use the schema object
680
+
681
+ PathValue.value disk cache
682
+ - If an entire server (PathValue server and Querysub) NEVER uses path values, we can move PathValue.values onto the disk, only reading them off disk when we send values over the wire
683
+ - This will have to be done explicitly, via a setting in AuthorityPathValueStorage
684
+ - The values will be read off disk by our serializer, which will ask AuthorityPathValueStorage to read all the real values off disk
685
+ - The FunctionRunner SHOULDN'T need this, as all of the values it stores should have been used semi-recently...
686
+ - Actually, only really only the PathValue server requires this, as otherwise we discard values after ClientWatcher.WATCH_STICK_TIME, which is presently 10 seconds!!!??? But even at a few minutes, that value won't be too low. What are we going to do, render 2GB of images at once? (and if it is video we need to stream it anyways, likely from another storage source, as there is no reason to use PathValues for videos...)
687
+
688
+ Optimizations
689
+ - Client storage of values, allowing the server to only send Time for large value the client doesn't have
690
+ - The client can just skip adding the PathValue, BUT, keep track of the last thing that is waiting to add. Then if the value is superceded anyway, it just won't add the full value, after it receives it.
691
+ - If the values are VERY large the server might be storing them on disk, so this allows the server to avoid even reading them off disk, which is extra efficient.
692
+ - One time subscriptions + polling subscriptions
693
+ - For large data that changes frequently this might be better?
694
+ - Sending very large values seems to cause something to block for a really long time. Not sure what, but... sending 100K writes with writeOnly, and dontStorePredictions, locks up for minutes at a time, then allows a lot of values to get through, then locks up?
695
+ - 10K writes is a lot better, and 1K writes is really fast.
696
+ - Maybe... writes on the websocket are buffered, and then the socket disconnects due to having too much send at once, and only reconnects later due to some random polling?
697
+ - MAYBE we just need better server enforced throttling?
698
+ - OR, if the problem are that the funtctions are getting rejected (which I am 99% is the problem), we should support "updateWriteTimeIfNeeded", and see if that fixes it?
699
+ - AND, we also need to handle streaming the updates better in FunctionRunner. It is kind of just batching everything, when it needs to wait (maybe with an "io" delay), to let the outputs clear?
700
+ - Delta supporting watcher
701
+ - Take all places that iterate over all paths, and have them work on deltas
702
+ - The callback will still have to process the changed paths directly (although it will have access to any state), and indicate the deltas to the watches
703
+ - This could be used in FunctionRunner to make it more efficient
704
+ - If we track when a path was created, wait longer to archive (2.5 times MAX_CHANGE_AGE), and then detect that is was deleted < MAX_CHANGE_AGE * 0.5 after it was created... we can avoid archiving it. But... then we need to clean up the tracking of when it was created, and that's a whole thing, so... it's just difficult...
705
+ - The server should batch forwardWrites when it receives them. That way if it is lagging due to a slow (synchronous) operation, a lot of values will buffer up on the network (but the batch queue will be empty, as we will have synchronously emptied it), and then when we finish the request we will have the batch time to gather up all the network requests (which should be a lot! as we can handle a lot of bytes in even just 10ms!).
706
+ - Of course, this adds ANOTHER 10ms delay, but... that should be fine, we won't get forwarded values often...
707
+ - We don't need to send back predicted values, when the prediction is accepted
708
+ - Client side we should only trigger watchers when a watched values actually changes (not just when there are any changes on the watches paths, at any point in history)
709
+ - Client syncing thrashing
710
+ - When we no longer need a value, we should stop immediately clearing it, and just mark it as unneeded, and then clear and unsync it a bit later on. This will take more memory, but allow navigation to be quite a bit faster
711
+ - Optimize parent accesses to not do brute force `.startsWith` searches, and instead maintain some kind of a lookup?
712
+ - Maybe... although, is this even much faster? It will certainly slow down writes.
713
+ - Optimize readLocks
714
+ - Each write stores readLocks very redundantly, making each function have `WRITES * READS` readLocks, which takes a lot to store, and requires a lot to synchronize.
715
+ - We can probably store a lockHash, to quickly detect duplicates
716
+ - Wire calls can use lockHash to depopulate duplicates, then re-populate them on the otherside.
717
+ - Such as commitWrites/forwardWrites, etc
718
+ - Our storage can depopulate duplicates when storing as well, maybe even storing readLocks in another file?
719
+ - Clear old pathValueProxy cache
720
+ - Better object comparison in certain values when we compare PathValue.value values? (only really clientside, I think?)
721
+ - As well as more object comparison checks, to not notify if the value is set to an identical value.
722
+ - Streaming / waiting during storage operations
723
+ - For example, we can stringify the file in chunks, instead of all at once, that way we don't block for > 100ms. We should wait ~10ms, giving a lot of requests time to clear.
724
+ - This isn't going to be especially big for large datasets, which may require merging everything at once, which with our current algorithm could take seconds!
725
+ - More efficient wire size and serialization speed
726
+ - If we write to 200K number values, which all depend on the previous states of 200K values, we end up sending 94MB, which takes 1200ms to serialize. We also receive a lot of data back for all the valid states. It might not be easy, but... it would be nice to optimize this, in some way.
727
+ - Probably via schema handling?
728
+ - ALSO, 94MB exceeds our default packet limit, so it doesn't even send! We could fix this with compression, but then it would take even longer to serialize!
729
+
730
+ Forward retry code
731
+ - We should maybe retry to forward writes if we can't forward them (and we aren't an authority)?
732
+
733
+ Wait a bit before loading values, in case a server wrote values, died, but the remote storage system isn't serving the read back yet?
734
+
735
+ Changing authorities bug
736
+ - If an authority changes, it is possible for it to receive values it doesn't care about, and then respond saying the results are valid. However, they aren't, instead it just ignored the values.
737
+ - ALSO, it is possible we will send a client ends up watching valid states from multiple authorities, due to being unsure if any are down, etc.
738
+ - AND, we could end up subscribed to multiple authorities for a single path (due to watchValueLocks), which will result in thrashing.
739
+
740
+ Prediction limiting / server overload protection
741
+ - If the PathValue server gets too far behind, we should stop predicting values, and start waiting for updates until the server can catch up
742
+ - This can also help when the server goes down, OR, more likely (hopefully), when the client's internet goes down.
743
+ - ALSO, the first thing to try is really to try switching servers, which will give us a form of automatic load balancing
744
+ - Although... if the limiting factor is our internet... then switching servers will actually make it worse. So... idk... this is actually difficult!
745
+ - MAYBE the server has to ask us to switch servers, and all we do when we find slowness is to slow down our writes?
746
+
747
+ Clientside function threading
748
+ - Clientside it would be fantastic, as it would allow render functions to NOT block the render thread. This means we could run multiple threads, keeping the application responsive even if a very slow component is rendering.
749
+ - We can convert dom event callbacks (or any closed values) to { functionContents, closedValues: { [variableName: string]: unknown } }. If the variable values are either serializable, OR, can be linked to exports (ex, misc_1.isEmpty), then we can recreate an identical function just by using eval.
750
+ - If we do this, we would we ALWAYS want to recreate the function. This ensures there is consistency if we call the callback cross thread, or on the same thread.
751
+ - We can store this style inside of the synced state, and make it available to be called directly.
752
+
753
+ Serverside function threading
754
+ - Required to kill very slow running functions
755
+ - They will of course we able to change their default timeout time
756
+ - They will need to be given an error result, to prevent them from rerunning
757
+
758
+ Serverside rendering
759
+ - Create a mounter which can go from vnode => string
760
+ - Clientside, probably render from scratch, and just have our clientside mounter check for existing DOM nodes (probably just when instructed to, which will happen on initial render, maybe... always on initial render?)
761
+ - Events COULD be an issue, or... we could just assume any unknown nodes have no event handlers?
762
+ - I THINK we can probably ignore events before we render, at least for version 1. The app is mostly a SPA anyways, so the time before render should be VERY short, and then everything should involve re-renders and NOT re-navigations. For version 2 we can capture everything globally via a built in script tag, and then re-trigger them once we actually mount (and either block, or delay links, so we don't get in a state of constantly trying to load/render, getting 50% of the way there, but then the user navigates via a link).
763
+
764
+ TESTS
765
+ - Tests which track the code used so we know which tests need to be rerun
766
+ - Render tests, with render JSX and paint it
767
+ - Can use a partial JSX => paint cache, so most of the time if the output doesn't change, OR, if the output doesn't change enough to change how it is painted, we don't have to repaint it.
768
+ - Also, a test harness that can click on vnodes and simulate their changes would be incredibly useful!
769
+ - This would get rid of the chrome javascript runtime step. We might still use puppeteer, but also... it is possible we could just use blink, or some other library, to just go from HTML => image extremely fast (realistically most pages should take < 50ms to render)
770
+ - True test coverage checking
771
+ - NOOP expressions (x + y becomes 0, etc, a function call that returns a number becomes 0, etc, etc), and see if any test changes. If not... then that line does not have test coverage
772
+
773
+ === Development tools feature complete | TASKS = 22 ===
774
+
775
+ Latency / load weighted node selection
776
+ - When they are multiple candidates, prefer the closer nodes
777
+ - ALSO, prefer nodes that have less load, allowing a form of automatic network balancing
778
+ - The ability to switch nodes would be nice as well
779
+ - Switching PathValueController nodes should be easy, we just subscribe everything to a new node, then disconnect from the previous node.
780
+ - We switch Querysub easily (using multiple), and disconnecting from the previous ones as soon as all calls on them have finished running (really that is true for most nodes, we don't want to disconnect when we have pending calls!)
781
+
782
+ Speculative function calls (calling functions that haven't been requested yet, just to sync the state they might need)
783
+ - Will be a bit involved, and might even require some clientside cooperation (ex, to detect which actions are possibly via which event handlers are being used), but... could result in a HUGE speed improvement
784
+ - Will have to determine what functions are most likely to be called
785
+ - We will likely need to give some context information for functions, or at least know their index, and maybe even page position? And maybe even the mouse position?
786
+ - If we run functions calls when the user moves their mouse towards a button we can probably increase our estimate quality with little effort
787
+ - We should test to see how long the mouse dwells over a button before clicking. Even 10ms would be useful. And also the average mouse speed. If we find users usually click after dwelling for say... 10ms, but take 20ms to click, we can get a 10ms headstart on the call!
788
+ - Will re-render after the click and then pre-sync the new reads needed
789
+ - If we do this on another thread, and somehow set the new syncs with low priority... this should be basically free (as if the user is moving their mouse around it isn't a background task, and we can at least monopolize a single core!)
790
+ - Will also pre-populate the clientside function cache, so when they really click it we can get the prediction very fast
791
+
792
+ Delta based render cache
793
+ - The best idea is to mark reads as being delta allowed, and THEN, marking output values as being reduceable. Then we just run with partial values given to the read, and merge the new writes with the old writes. IF the writes differ in any places that are not reducable... then we have to run with the entire set of data (and it results in a warning). But if not... it just works!
794
+ - The reduce functions can be
795
+ We already know the delta when we re-run watchers (as we reran them for a reason!),
796
+ - Maybe at the component level? Although... I kind of want to implement it for just arbitrary data
797
+ - A function will have the ability to read from an object/list with a "delta" read
798
+ - It will still receive the object/list, it might just partial
799
+ - EXCEPT, we might need to return special state for deletions?
800
+ - If the only reads of the object/list are delta, then the next time there is a change, only the new/changed values are provided
801
+ - The output is analyzed, and the differences between the new output are found
802
+ - For every different write, we determine the update technique
803
+ - This will probably involve setting some special value on the write?
804
+ - Examples would be "sum" or "join" or "insertSorted"
805
+ - The most generic case receives the previous state (that has changed, already drilled down), and the new state, and updates the previous state (in memory) so it now has the new state as well.
806
+ - We need to handle writes that propagate deletions
807
+
808
+ Blocking FunctionRunner call mode
809
+ - Will probably be slower in most cases though, as each value accesses required a round trip with an authority to read the value
810
+ - Might be useful for code with expensive side-effects?
811
+ - Add a BLOCKING mode to PathValueProxyWatcher, which uses Atomics.wait to block until a read value is synced.
812
+ - This potentially allows for efficient function evaluation, by not requiring any code to rerun.
813
+ - This is only possible if something can mark a value as synced on another thread, and absolutely not if we are single threaded (Atomics.wait wouldn't even work on the main thread of a single thread app anyways)
814
+
815
+ Function results!
816
+ - Specially marked "specializedHardwareCalls" functions will be able to "return" a result
817
+ - Only possible with blocking FunctionRunner mode, as for the functions we want to return results we also only want to run them once (for expensive operations)
818
+ - Of course, this doesn't prevent: 1) slowness cascasding syncing, or 2) result invalidation
819
+ - Hmm... we also want the ability to cancel calls?
820
+ - Maybe calls can do this without themselves?
821
+ - Hmm... if the calls are so slow... maybe the user should just manage a queue themselves, so they can see what is going on? It really isn't that hard, and if they have to manage special hardware anyway... then it won't like this would prevent them from just quickly scaling a function?
822
+ - At this point we should have support for unsafe function writes. We MIGHT want to make the writes from this unsafe by default?
823
+ - By default probably allow write time adjustment, to prevent invalidation (at least when we start the function call)
824
+ - The return object will be { type: Symbol("hardwareCall"), call: number, path: ".,querysub.com,.test" }
825
+ - ALSO, unioned with a type for an error { type: "error", errorMessage: string; errorMessageFull: string; }?
826
+ - The function return types will be modified so any callers have to handle this symbol type
827
+ - The proxy watcher will temporarily register to receive all calls, so it can call them
828
+ - The proxy watcher will search the output writes for these values
829
+ - Only shallowly, which is fine for now, as if they nest it I believe the Symbol will cause a throw, so the wrong value won't be written and not noticed
830
+ - The proxy watcher will then actually construct the call objects, telling them where to place their results
831
+ - When the FunctionRunner evaluates the calls, it will place the results in the specified paths
832
+
833
+ Support VERY large data sets (> local disk size)
834
+ - If an authority hasn't been used for a while, dump the memory for it, and... become "unready". Then, if someone accesses it, go about loading it again.
835
+ - This only works if we are sharded, but there isn't really a better way.
836
+ - We should test this with some very large values.
837
+
838
+ Automatic sharding of ValuePath nodes
839
+ - For KVP nodes as well as Function nodes
840
+ - More important for function nodes, but... it could help for KVP nodes as well?
841
+ - IF we have high contention, and many servers, the resolving process takes SERVER_COUNT * SERVER_LATENCY, which could be high. Automatic balancing can help fix this, by finding a way to identify overlapping writes via reads (ex, spatially hash read paths, and if those hashes are ===, see if the spatial hash of the write paths are as well).
842
+ - This reduces the SERVER_COUNT involved in contention, which... fixes the issue!
843
+ - We will need to support remotePathAuthority to get this to work
844
+ - Needs to be somewhat generic, so we can use it for both PathValueController, and FunctionRunnerController
845
+ - Nodes will inform their parent path nodes of their utilization, so parent nodes can redistribute child nodes accordingly
846
+ - Oversaturated nodes will split, pushing newer nodes down to more specific traffic
847
+ - Ex, from ["x"] to ["x", "x.[0 <= hash < 0.5]"], ["x.[0.5 <= hash < 1]"]. AND, when a node has enough descendants, it will stop with all child paths, and the parent will just be ["x"]
848
+ - BUT, we should only shard when needed. Otherwise syncing parents can become overly expensive? (Ex, if we have 100 servers, we really wouldn't want to split a small collection across 100 servers, as then it would take a huge amount of work to sync the keys, and if there are < 100 keys, it would be needlessly slow)
849
+ - Nodes who have parents that have insufficient members will remerge with their parent (perhaps completely taking over the parent, if the parent completely disappears)
850
+ - Nodes with child nodes with insufficient members will redistribute, or remerge into one path, as needed
851
+ - Test on a local machine, spawning many processes, killing them, etc
852
+ - We need to fix our path limitation thing
853
+ - Detect the size limit of our underlying file system, and use that
854
+ - This allows us to turn on long paths on windows
855
+ - WARN if long paths are not enabled
856
+ - Use this both to constrain balancing AND to have archives write to the most specific path possible, within the current limits
857
+
858
+ Programmatic digital ocean droplet launching
859
+ - We will want some kind of administration page that the app serves by default, requiring NetworkTrust and localhost to access.
860
+ - From this page we should be able to:
861
+ - import digital ocean api keys
862
+ - see all currently configured droplets
863
+ - launch new droplets
864
+ - All launched droplets will be automatically configured with the code, given trust, and configured to auto restart all the necessary process, etc (so they should be able to run unattended after starting)
865
+ - This means we can get rid of the emails to add trust, as nodes will no longer just "appear", but instead by instructed to appear, at which point we can explicitly ask for their public key and trust it (we should never provide the key, as this is incompatible with a transition to hardware keys).
866
+ - We also want the ability to monitor traffic, and make recommendations about having less/more droplets
867
+ - These recommendation should display in the UI
868
+ - There should be daily or weekly emails about recommendations to some admin email account.
869
+ - EVENTUALLY we might have these recommendations automatically followed, for scaling, although maybe only to certain degrees, as automatic scaling can be dangerous.
870
+ - AND, it would be nice if we could view/administer MULTIPLE domains at once, OR AT LEAST summarize traffic recommendations for multiple domains at once.
871
+
872
+ Automatic digital ocean droplet launching
873
+
874
+ History for the purposes of reverting data
875
+ - EVEN just backups works, but... storing a transaction log is preferred
876
+ - If we store writes, we can re-simulate to a previous point in time. It might be slow, but it is better than losing all your data due to a database drop.
877
+ - If we store clobbered data as well, then we will be able to restore even if our history size exceeds the max and needs to be truncated
878
+ - Kind of required if we want to use it to store any important data...
879
+
880
+ Fix hardcoded email and TOS accept in certAuthority.ts
881
+
882
+ AST parsing to detect global side-effects in functions
883
+ - Global side-effects are going to break things. We should at least mark root functions that do this, OR, that have any child calls that do this.
884
+ - Who knows what libraries we call are going to be doing, they could have all kinds of unintended side effects involving the disk and who knows what else!
885
+ - The strictest mode would be the disallow global side-effects unless it is enabled per function call
886
+ - ALSO, AST parsing to detect module side-effects (that impact values outside of that module)
887
+
888
+ Write proxy replacing
889
+ - If we have a lot of local writes it can get slow, due to the proxy. If we replace the writes with function calls it could allow a lot more local writes.
890
+
891
+ Schemaed proxy replacing accesses
892
+ - Instead of passing an array, find common accesses and replace with a call to a factory function to create a schema, which generates a function, which we then pass the remaining variables to
893
+ - Try to put all of these beside the databaseTyped (or at least at a module level!), so we can view the code without source maps and see all the examples of accesses. Their will be 1 for each line that reads from the database (that we can find)
894
+ - If we set this schema in the write itself we can greatly reduce write overhead (especially if we have keys that are numbers).
895
+ - We can send a binary format to forwardWrites and onValues
896
+ - Maybe just for the wire communication. But sending a buffer as the payload, instead of a value.
897
+
898
+ Fix multi authority writes
899
+ - Multi authority writes are annoying, because it causes issues with predictions. As in, you write, predict your value, but then have to wait because one authority is taking a long time to resolve. THEN, another write uses our prediction, but this time only uses the fast authority. This results in the fast authority rejecting our write, until we can send it the full writes, which may take a while if the slow authority isn't resolving!
900
+ - AND, if the slow authority takes a REALLY long time, we might never commit the write, resulting in a valid state, BUT, we can no longer commit any writes! Although, maybe this state is okay, as if we can't write the full write... we probably shouldn't write?
901
+ - And... maybe this whole issue is fine, as the authorities will eventually resolve, and then it will be fine...
902
+
903
+ === Shippable features complete | TASKS = 12 ===
904
+
905
+ Triggers / data pipeline / autorun
906
+ - Basically an abstraction of what FunctionRunner does (where it watches the database for values, and then writes other values to the database)
907
+ - Setup via a value in the database
908
+ - Like a function write, but, this function reruns every time anything it read changes
909
+ - Will keep the same thread (maybe a dedicate thread?) for a long period of time
910
+ - The ability to watch data, calling a non-committed function when it changes
911
+ - The function that is triggered can call synced functions, committing values
912
+ - Will be able to sync a lot of state, and also keep values in untracked state (if it wants to)
913
+ ALTERNATIVE
914
+ - Just use FunctionRunner, and if you need to use dedicated hardware, use another domain
915
+
916
+ 1.3) Buffer support in SocketFunction? Hmm... I mean, if we have GB sized files, that would required... And sending a 20GB video file is maybe feasible? Hmm... or... maybe not? Maybe it is more useful for 1MB chunks, which we don't want to have to decode each time? So this is really just "increase data throughput"?
917
+ - Essentially, we want a JSON.stringify that converts to a Buffer, and also supports Buffers.
918
+ - We will probably eventually support threads, so... maybe we just use Accessors2? Hmm... Or at least some rework of Accessors2?
919
+ - Would also need to support it in function argument serialization / deserialization
920
+
921
+ Error notification system
922
+ - Some kind of immediate notification + email digest system
923
+
924
+ Runtime FunctionController type checking
925
+ - We should check the type of all arguments against their typescript type by default. That way accidental prototype contamination is less likely. And maybe there are other security vulnerabilities?
926
+
927
+ Backups
928
+ - Taking our entire database, and backing it up periodically
929
+ - We can probably check the time in the last backup, and use that to infer what values will definitely be in it, using this to backup less data (nothing that is already backed up), making this efficient (to write, not necessarily to read)
930
+ - Once in a while we would want to write the full state, otherwise after a few thousand backups restoring could take forever.
931
+ - We can also be somewhat efficient about reading from the database files, using block create time to know if they are only old and already backed up data.
932
+ - At this point we will already have a management interface, so... this should be configurable via that
933
+ - We will have to think about how to rate limit it. A lot of databases would probably work by just streaming every single write, then compressing them after they exceed a certain total size.
934
+ - AND, when we compress, we probably want to default to the "compress old data more", so we have logarithmic seeking capabilities.
935
+ - Might be made redundant by the history navigation feature?
936
+
937
+ === Feature complete | TASKS = 3 ===
938
+
939
+ Path queries for synchronization
940
+ - We could support a way to query paths (likely via a parent selector), to do synchronizations. It makes it slower for the server to continue to send values (each new value required evaluating the selector), but... it could make selecting large amounts of data (millions of values) much faster?
941
+
942
+ Local network call forwarding / neighbor prediction
943
+ - If there is a write on the local network we can get faster then waiting for it go to the server or back (by local network it mostly means on the same machine, but different process / browser tab / thread).
944
+ - We will need to ingest these values as regular predictions, except because we aren't writing them we also need to watch for their valid states
945
+ - We also aren't getting if the write is even commited or not (which tells us if it was EVER valid), so... we basically need to assume the value is invalid if (AFTER the valid watch is done), we don't get a valid notification after a short period of time
946
+ - We also need to validate writes coming from neighbors, to ensure they are recent enough, and maybe we should do some other checks?
947
+ - Having this work for function writes would be nice, but... we REALLY have to trust the neighbor for this to work?
948
+ - Well... we could always run the permissions checks? In which case... I guess it would be safe?
949
+ - Oh, well.. maybe permissions checks aren't that important. If they are on the same network they are sitting in the same room, so they are somewhat trusted!!!
950
+ (And worst case they just cause the data to temporarily change, which isn't the end of the world...)
951
+ - AND, we can have neighbor prediction off by default, and only turned on for certain application (maybe on by default in the browser).
952
+
953
+ Function call syncing (as opposed to PathValue syncing)
954
+ - If we have a physics based system where we predict the physics loop every tick, we can predict a HUGE amount of values (millions), possibly even using the GPU. HOWEVER, the server won't understand our prediction efficiently, and so it will need to send as all the values anyways!
955
+ - If instead of syncing the paths we use, we sync the functions that impact these paths, we can have a massive state be kept up to date without saturating the network
956
+ - We will have to be told the hash results of these functions, to ensure we are calculating them correctly.
957
+ - So, a server instance will have to basically watch all the paths itself, to figure out which functions apply.
958
+ - And... presumably the results need to be committed anyways, otherwise this is just a different database! Although... if the function runner and KVP owner are on the same machine, the transfer of paths should be somewhat efficient? And only snapshots need to be saved, so... this could work?
959
+ - Basically, you would run the KVP node, and the function runner, on the same machine.
960
+ - Some kind of pipeline would trigger the physics function to tick every frame
961
+ - This pipeline would be also predicted on the clientside, so it can consistent know about ticks
962
+ - Eventually the client will know what functions were run (it should be pretty accurate at knowing this!), And the hash results. The hash results can come later, as the client will know that it will have to simulate old physics results a few times as course changes trickle in.
963
+
964
+ The ability to mark writes as "no persistent storage"
965
+ - Useful for things such as streaming video, which can then have a pipeline that combined it and stores it in B2 (the pipeline would mark what is stored in B2 and the urls for that content, and the rest of the data would be available on the network directly)
966
+ - This makes live streaming possible, but also accessing terabytes of historical data.
967
+ - This is different than just video uploading, which should go directly to B2, because it trickles, requiring something to aggregate it, while also requiring instant access.
968
+
969
+ Read function triggers
970
+ - Allowing setting up a value that calls a function every time it is read, to produce the data (and non client predictable function)
971
+ - Allows for exposing an off-network source (like a database) to the network
972
+ - By default will just mark everything as valid, BUT, we could add valid ranges
973
+ - For databases like mongodb we could roughly do it via changestreams, to warn if the value has changed
974
+ - For blockchains we can perfectly detect if the value has changed
975
+ - For the writing to external sources side... we would need "unsafe" functions, that don't add any readLocks (so they can never become rejected). Because we can't reject values in external sources!
976
+
977
+ FunctionController path syncing
978
+ - For small number of reads, the latency of the read is acceptable for the trade off of reduced complexity of FunctionController.
979
+ - HOWEVER, for functions with a HUGE amount of reads (GBs of data), we will want to sync it instead...
980
+ - We still need to run historical functions, so... we basically need to:
981
+ 1) Ask for ALL future writes on a path
982
+ 2) Ask for all writes on that path (up to what the authority stores, which is up to and including the last golden value, which should be enough history)
983
+ 3) Sync the valid state of all writes we receive, forever
984
+ - The hard part, is trying to keep the paths synced simple. If we just sync every access... then we could be syncing tens of millions of paths... which means not only does an authority have to store that much data, it also has to store watchers for all of it... which... will probably be slow?
985
+ - Although... maybe not... it is just a Map<string, string>?
986
+
987
+ Support arrays, via adding another transparent property
988
+ - Basically the object properties, BUT, it will specify it is an array
989
+ - The proxy will have to be told of this, and... function properly?
990
+ - I think if it's object is [] it will work? Maybe? We should double check how proxys handle arrays...
991
+ - Does push just map to a set plus a .length update?
992
+ - And what about splices!
993
+ - This will be annoying, and probably slow...
994
+
995
+ Actually Object.keys() ACID handling
996
+ - Ugh... very annoying, and difficult
997
+ - A path authority can sync child keys for a range of time (similar to how it syncs a path for a range of time), and then compute a hash of them (sort and sha256). It won't be efficient, but... it should work.
998
+ - Will be OPT-IN, because the slowness of hashing the keys is almost never worth the ACID nature of it
999
+
1000
+ Object.keyRanges()
1001
+ - If we use search patterns that line up with our range sharding, we can efficiently access ranges of keys
1002
+ - We will probably need to add more details in our range sharding
1003
+ - We might want to specify sharding characteristics via some callback near proxyWatcher.databaseTyped
1004
+ - Ex, schema.dataSharding(database => [{ path: database.logs, map: key => +key }]
1005
+ - Ex, schema.dataSharding(database => [{ path: database.maps, map: key => key.split("_")[0] }]
1006
+
1007
+ Old data cleanup ability
1008
+ - Some way to actually delete old data
1009
+ - Maybe via both setting all values to undefined, and having nested undefined no longer stored
1010
+
1011
+ Ability to selectively undo history
1012
+ - Probably recursively by path
1013
+ - We would than have to store history under those paths
1014
+ - Probably with a limited history (ex, 30 days?)
1015
+ - The ability to select changes by values / by time, taking everything in the same changeset (via looking at readLocks), and adding a new change that reverts the change (if the revert value is still the latest)
1016
+ - Ex, undo all changes by user for the past day, one specified paths
1017
+
1018
+ Disk support
1019
+ Local disk support instead of backblaze
1020
+ - AKA, implementing backblaze using the local disk of many nodes
1021
+ Recent changes disk backup
1022
+ - Store changes that are too young to be archived on disk
1023
+ - CAN'T use archive the values, as those values are assumed to be valid, so... needs a new format, which is just used to store changes, in case of a crash.
1024
+ - If we have a single node, we can stream our changes to the disk, reducing our data loss on crash from ~10 minutes, to < 10 seconds, with very little disk IO
1025
+ - We can have a flush time option, which can be set even lower, which will increase IO, but can reduce losses from crash to any point we want.
1026
+
1027
+ C++ (.cpp) import support
1028
+ - Via WASM compilation, which can be done via https://github.com/sliftist/cpp-portable-loader and `clang-wasm`
1029
+
1030
+ C# import support / other language child processes
1031
+ - Would only work on the serverside
1032
+ - Would have to run a child process which runs the C# code?
1033
+ - If we are going to run another process, we can just run any easy scriptable language. Python would be easy to get working as well. On a smaller scale, Java would work too.
1034
+ - Ideally we automatically generate typings, but... even if we didn't, that would be fine.
1035
+ - C++ would be useful too! As a lot of the time this is useful for making OS calls, which are easiest in C++.
1036
+
1037
+ Rust import support
1038
+ - Compiling Rust to wasm is even easier than C++, so... we could probably do it fairly easily
1039
+ - I'm not sure how to get typings, although they aren't strictly needed
1040
+
1041
+ === Advanced optimizations | TASKS = 2 ===
1042
+
1043
+ === \/ Speculative tasks \/ ===
1044
+
1045
+ - function call results
1046
+ - Some way for function calls to return T|undefined, and be given the location they are assigned to, so they can automatically write the result there
1047
+ - Is basically the same as adding the path, so... this is probably a good way to do it.
1048
+ - Can still with with function predict!
1049
+ - Passing the path to the function call is a bit annoying, but... if we return a special value, we can observe where that gets written to in the proxy, so... it isn't so bad...
1050
+ - We can even allow writing to multiple output locations
1051
+ - Really only makes sense if the function is slow. Otherwise, is should just be an inline function!
1052
+ - We should add in function level sharding, so we can exclude function calls from function runners that don't opt into those. That we can split up slow functions from regular functions.
1053
+
1054
+ - "limits", to limit resource strain any node can put on PathValueServer
1055
+ - limit the total number of paths watched
1056
+ - limit the number of writes per second
1057
+ - limit the total bytes per second of writes
1058
+ - maybe limit onValue callbacks, depending on size, etc
1059
+ - Maybe dynamic limits, depending on load
1060
+
1061
+
1062
+ - Fix our JSON serialization code converting undefined to null inside of arrays
1063
+ - This impacts function calls, which can be really annoying / confusing...
1064
+
1065
+ - Actual query subscriptions
1066
+ - Tell the querysub server that function we want to run, and everything it watches will be returned
1067
+ - Anything the server misses can be additionally subscribed to
1068
+ - This allows the cascading latency penalty to be equal to the querysub server latency. WHICH, if it is on the same server as the PathValue server, can be less than 1ms (we will need to turn off some automatic batching to lower it this far)
1069
+ - AND, if the querysub server did a full PathValue sync, asking for all values on all paths... the latency would be 0.
1070
+ - We would need to be able to fit the entire database into memory, but... if we allowed `watchRecursive(path)`, and either shared Querysub, OR, had queries explicitly map to the path they wanted (which hopefully would either be small, or shared with another server), then... it would just be efficient...
1071
+ - AND, the client could even subscribe to `watchRecursive` as well?
1072
+
1073
+ Better FunctionRunner startup
1074
+ - We should sync all of the repos we will need BEFORE taking any shard space? As the clone could easily take minutes, which would cause functions to timeout.
1075
+ - Although... maybe just having it better distributed would solve that problem, as other nodes should automatically pick up the slack?
1076
+
1077
+ CORS support, so querysub can be accessed by other domains
1078
+ - We would read the CORS values from the database, and they would be set in, or similar to "yarn shard-deploy" / "yarn deploy"
1079
+ - Only querysub needs CORS, as the browser should know to only access it
1080
+
1081
+ Serverside "local" writes
1082
+ - Update code to special case "local" writes, so that we never sanitize remote writes so they never use ReadLocks on "local" writes.
1083
+ - BUT, allow "local" writes to have ReadLocks on remote writes (IF we are trusted on that domain). Our system should automatically subscribe to valid states, and maintain the valid state of "local" writes. This prevents our "local" values from having invalid values that stick around (as in, in a cache), even though we might end up writing them to a "remote", which would make them invalid.
1084
+
1085
+ Have deploy log the % files and % lines that are NOT allowclient
1086
+ - This determines which files we might serve clientside
1087
+ - We only really care about files NOT in node_modules, but, in our folder?
1088
+ - Although, maybe we should consider any private npm packages as well?
1089
+
1090
+ Lazy Function / Function Prioritization
1091
+ - If we have functions that can run at any time (but must run in order), then... we can run them later, when the server is more free.
1092
+ - AND, we can even go as far as to detect when the predicted outputs of them are needed, running the functions at those times?
1093
+ - Maybe we would actually want this to be functions triggered one read, but only triggered once?
1094
+
1095
+ Ability to disable syncing of paths
1096
+ - Possibly depending on context, such as page
1097
+ - Also, possibly just to reduce the update rate for certain paths
1098
+ - This fixes the issue of lag (either network, client, or server), when many pages use a commonly changed value
1099
+ - Ideally we WOULD like it to be updated, but... if we don't need it updated, and 99% of our server capacity is spent updating a few values we don't care about...
1100
+ - We want this to be able to be added post-hoc, potentially live (at a data level, not a code level),
1101
+ so the site can be quickly optimized on the fly
1102
+ - But of course, the code will also need the ability to specify it doesn't want updates
1103
+ - Will basically function as a watchLatestOnce, where it gets the latest value, and then no more.
1104
+
1105
+ Better undefined support
1106
+ - We COULD make it so for the last run of a function, after all values are synced, we run again returning undefined for every value that is fully synchronized with no child values synchronized.
1107
+ - If the first run has every value synchronized we would have to run again.
1108
+ Pros
1109
+ - This MIGHT make function run more naturally
1110
+ Cons
1111
+ - Functions are more complex to run (and must run more times)
1112
+ Alternatives
1113
+ a) Just use atomicObjectRead if you want to read undefined
1114
+ b) Use `+x`, `x + ""`, OR `${x}`, to coerce it to a primitive
1115
+
1116
+ Support instances in our data?
1117
+ - Benefits
1118
+ - Instead of passing around string ids everywhere, and having programmers have to know which functions use it, you can pass around a class and just get autocomplete
1119
+ - Costs
1120
+ - Slow
1121
+ - Harder to debug
1122
+ - Alternatives
1123
+ - Compile time instances => static function + id call
1124
+ - Basically, we can "store" and "pass" around an instance, and even "new" it. But
1125
+ new => create random id, and store data in static location under that id
1126
+ function calls => call static function with instance (which is a string) as a parameter
1127
+ instance accesses (including this.) => lookup data from root with static lookup
1128
+ - This is harder to do, but... faster, and easy to debug.
1129
+ - Function modules with local .databaseTyped call
1130
+ - You can go to defintion and see all the exported functions, which are probably how you mutate the data (especially if the .databaseTyped call isn't exported!)
1131
+ - BASICALLY, everything starts as a static call. But... those calls may wish to create class instances, and store those (ex, binding data and functions)
1132
+ - SO, on seeing a set that is a class (ex, has functions), instead of just stripping the functions, we...
1133
+ - Figure out if it is a deployed function (has exposedRootClass decorator).
1134
+ - If not, we don't store it
1135
+ - Store a new InstanceId in our schema with the data provided
1136
+ [DomainName][ClassName].Instances[InstanceId].Data = Data
1137
+ - Replace the write with this new instanceId
1138
+ - On accessing a value that... looks like an instanceId? We...
1139
+ - On reading values we use the Instances.Data path instead
1140
+ - On calling functions we load the class from the Source info, and call it
1141
+ - Specifically, providing a this context equal to the data value.
1142
+
1143
+ Clientside "local" FunctionRunner
1144
+ - In theory, some operations will heavily depend on some server function call completely successfully. BUT, also store local state?
1145
+ - IF we never need this, then we never need to implement it. But... if we find a case where this is useful, in which just moving the data to the remote is not viable (and there is no alternative)... then... we could do it?
1146
+ 1) Allow "local" functions to run using FunctionRunner, so they can automatically rerun when rejected
1147
+ - We will require the ability to mount FunctionRunners under the "local" domain explicitly
1148
+ 2) Allow (when specified) "local" functions to depend on "remote" writes
1149
+ - This requires allowing syncing valid state, etc
1150
+ - Without runing FunctionRunner rejections are usually not preferred. But with running it, rejections can result in a rerun, and so are not as bad.
1151
+
1152
+ Intercept DB, to allow writes without actual ownership of a domain, cascading to store all values that depend on these changes as well.
1153
+ - Maybe only useful for hacking together application, or development, but... seems like it would be pretty cool?
1154
+
1155
+ Function call results (for cross domain calls)
1156
+ - Probably not a good idea. Maybe... just never do this...
1157
+ - Non-cross domain calls are all inline anyways, so this is only for cross domain calls
1158
+ Something like: QuerySub.afterFunction(() => OtherController.queueAndRun(), result => TestController.finished(result)), which tells FunctionRunner to do some extra stuff after a cross domain call finishes.
1159
+ - We COULD ALSO use async/await. We KNOW when the synchronous part of a function finishes
1160
+ - Ex: `let result = await QuerySub.afterFunction(() => OtherController.queueAndRun())`
1161
+ - As we are broken in on the machine, we don't need ALWAYS resume (although if the node is killed, and the function needs to be rerun, we would need to re-evaluate the previous call to catch up).
1162
+ - We would need know when the await returns, wrapping it with some code?
1163
+ - This is... impossible? Although... if the thread is CLEAN, we could just assume the next function will run, resolve, then wait for a promise, then assume the function is finished? It gets REALLY difficult
1164
+ - This will break up the ACID nature of the function, which is fairly unfortunate
1165
+
1166
+ Size optimization
1167
+ - Move register calls into a .client.ts file, so browsers only need to import the interface, and not the implementation too!
1168
+ - Better HTTPS endpoint which prebundles some calls, so we can bootstrap faster
1169
+
1170
+ Streamlined externals, via a custom site that takes payment and handles setting up all the other services
1171
+
1172
+ Lazy connections
1173
+ - NetworkState creates a lot of connections, which have very infrequent traffic. Instead of having N network connections, we should allow some connections to be marked as less important, and closed when not used for a bit. Although... if we say had, 10K open TCP connections, is this a problem? Hmm...
1174
+
1175
+ Synchronize time ourself
1176
+ - We can't trust machines to synchronize their time. Synchronize it ourself in NetworkState (OR, maybe just by using NetworkState), and use that instead of Date.now() everywhere!
1177
+ - We'll want to use a waitForTimeToSync() function in places to gate our major code locations to ensure we always have an synchronize time, so we can expose a getTime() function that is synchronous.
1178
+
1179
+ Replace ALMOST all external services
1180
+ - We can run our own DNS server
1181
+ - We can replace firestore with our regular storage controller
1182
+
1183
+ Very short expiry times on thread certificates, finding some way to automatically update them while running
1184
+ - At first only node <=> node
1185
+ - BUT THEN, our proxy should be able to handle updating the peer cert, as it can probably call renegotiate?
1186
+ - Our system for the client to update it's cert after the fact can likely be reused to trigger an update of credentials?
1187
+ - I assume outstanding connections won't be killed if their certs expire, so... we would actually want to
1188
+ add an additional check to close a connection if it's cert isn't updated? Or... we could just call renegotiate, which
1189
+ I think serverside TLS connections can do?
1190
+
1191
+ Public release?
1192
+ Get benchmarks on https://benchant.com/ranking/database-ranking
1193
1193
  - It seems like the throughput can be easily beat. We trade massive data-loss and memory efficiency for speed, so after our path optimizations we should be able to easily beat all others servers (again, because we lose data easily, and store everything in memory...)