@cnstra/core 3.0.0 → 3.2.1
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.
- package/README.md +51 -34
- package/dist/CNS.d.ts +5 -2
- package/dist/CNS.d.ts.map +1 -1
- package/dist/CNSCollateral.d.ts +1 -0
- package/dist/CNSCollateral.d.ts.map +1 -1
- package/dist/CNSInstanceNeuronQueue.d.ts +2 -1
- package/dist/CNSInstanceNeuronQueue.d.ts.map +1 -1
- package/dist/CNSNetwork.d.ts +10 -3
- package/dist/CNSNetwork.d.ts.map +1 -1
- package/dist/CNSStimulation.d.ts +40 -5
- package/dist/CNSStimulation.d.ts.map +1 -1
- package/dist/createCNS.d.ts +4 -0
- package/dist/createCNS.d.ts.map +1 -0
- package/dist/factory/index.d.ts +13 -12
- package/dist/factory/index.d.ts.map +1 -1
- package/dist/index.cjs +512 -254
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +506 -249
- package/dist/index.js.map +1 -1
- package/dist/types/TCNSModalitySelection.d.ts +13 -0
- package/dist/types/TCNSModalitySelection.d.ts.map +1 -0
- package/dist/utils/cnsComposeModalityResponse.d.ts +2 -0
- package/dist/utils/cnsComposeModalityResponse.d.ts.map +1 -0
- package/dist/utils/cnsResolveModalityHandler.d.ts +4 -0
- package/dist/utils/cnsResolveModalityHandler.d.ts.map +1 -0
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
// src/CNSStimulation.ts
|
|
4
4
|
var TASK_DENDRITE = /* @__PURE__ */ Symbol("cnstra.taskDendrite");
|
|
5
|
+
var TASK_ACTIVE_POSITION = /* @__PURE__ */ Symbol("cnstra.taskActivePosition");
|
|
6
|
+
var TASK_PENDING_POSITION = /* @__PURE__ */ Symbol("cnstra.taskPendingPosition");
|
|
5
7
|
var CNSDendriteContext = class {
|
|
6
|
-
constructor(stimulation, abortSignal, cns) {
|
|
8
|
+
constructor(stimulation, abortSignal, cns, task) {
|
|
7
9
|
this.stimulation = stimulation;
|
|
8
10
|
this.abortSignal = abortSignal;
|
|
9
11
|
this.cns = cns;
|
|
12
|
+
this.task = task;
|
|
10
13
|
}
|
|
11
14
|
// Prototype getter, not a constructor field: you only pay a single `cns.global`
|
|
12
15
|
// read on the dendrites that actually reach for it. Value getter, so
|
|
@@ -21,9 +24,15 @@ var CNSDendriteContext = class {
|
|
|
21
24
|
get stimulationContext() {
|
|
22
25
|
return this.stimulation.options?.stimulationContext;
|
|
23
26
|
}
|
|
27
|
+
// This activation's own store - written by a response listener before the
|
|
28
|
+
// activation ran. Prototype getter like the two above: nothing is allocated
|
|
29
|
+
// for a handler that never reaches for it.
|
|
30
|
+
get activationContext() {
|
|
31
|
+
return this.task?.activationContext;
|
|
32
|
+
}
|
|
24
33
|
};
|
|
25
34
|
var CNSStimulationResponseImpl = class {
|
|
26
|
-
constructor(stimulation, inputSignal, outputSignal, queueLength, pendingActivations, activeActivations, hops, error,
|
|
35
|
+
constructor(stimulation, inputSignal, outputSignal, queueLength, pendingActivations, activeActivations, hops, error, neuron, startedAt, duration, scheduledActivationTasks = EMPTY_TASKS, activationContext = void 0) {
|
|
27
36
|
this.stimulation = stimulation;
|
|
28
37
|
this.inputSignal = inputSignal;
|
|
29
38
|
this.outputSignal = outputSignal;
|
|
@@ -32,9 +41,15 @@ var CNSStimulationResponseImpl = class {
|
|
|
32
41
|
this.activeActivations = activeActivations;
|
|
33
42
|
this.hops = hops;
|
|
34
43
|
this.error = error;
|
|
35
|
-
this.neuron =
|
|
44
|
+
this.neuron = neuron;
|
|
45
|
+
this.startedAt = startedAt;
|
|
46
|
+
this.duration = duration;
|
|
47
|
+
this.scheduledActivationTasks = scheduledActivationTasks;
|
|
48
|
+
this.activationContext = activationContext;
|
|
36
49
|
}
|
|
37
50
|
};
|
|
51
|
+
var EMPTY_TASKS = [];
|
|
52
|
+
var EMPTY_SUBSCRIBERS = [];
|
|
38
53
|
var CNSStimulation = class {
|
|
39
54
|
constructor(cns, instanceNeuronQueue, options, onResponse, onDrain) {
|
|
40
55
|
this.cns = cns;
|
|
@@ -63,8 +78,14 @@ var CNSStimulation = class {
|
|
|
63
78
|
this.onDrain = onDrain;
|
|
64
79
|
this.instanceNeuronQueue = instanceNeuronQueue;
|
|
65
80
|
this.onResponse = onResponse;
|
|
81
|
+
this.concurrencyLimit = this.options?.concurrency ?? Infinity;
|
|
82
|
+
this.abortSignal = this.options?.abortSignal;
|
|
83
|
+
this.subscribersByCollateralIndex = cns.network.getSubscriberTable(
|
|
84
|
+
this.options?.modality,
|
|
85
|
+
this.options?.afferentPath
|
|
86
|
+
);
|
|
66
87
|
if (this.options?.maxNeuronHops) {
|
|
67
|
-
this.
|
|
88
|
+
this.neuronVisitCounts = [];
|
|
68
89
|
}
|
|
69
90
|
}
|
|
70
91
|
pushFailedTask(failure) {
|
|
@@ -81,8 +102,7 @@ var CNSStimulation = class {
|
|
|
81
102
|
}
|
|
82
103
|
// ----- Inlined activation queue / pump -----
|
|
83
104
|
get canStartOperation() {
|
|
84
|
-
|
|
85
|
-
return this.activeOperations < limit && !this.options?.abortSignal?.aborted;
|
|
105
|
+
return this.activeOperations < this.concurrencyLimit && !this.abortSignal?.aborted;
|
|
86
106
|
}
|
|
87
107
|
qResize() {
|
|
88
108
|
const oldCapacity = this.qCapacity;
|
|
@@ -139,48 +159,56 @@ var CNSStimulation = class {
|
|
|
139
159
|
return;
|
|
140
160
|
}
|
|
141
161
|
this.pumping = true;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (this.
|
|
157
|
-
|
|
162
|
+
try {
|
|
163
|
+
while (this.canStartOperation && this.qSize > 0) {
|
|
164
|
+
const task = this.qDequeue();
|
|
165
|
+
this.activeOperations++;
|
|
166
|
+
const ret = this.executeActivationTask(task);
|
|
167
|
+
if (ret && typeof ret.then === "function") {
|
|
168
|
+
this.addPositionedTask(this.activeTasks ?? (this.activeTasks = []), task, TASK_ACTIVE_POSITION);
|
|
169
|
+
ret.then(
|
|
170
|
+
(cb) => {
|
|
171
|
+
this.activeOperations--;
|
|
172
|
+
this.removeActiveTask(task);
|
|
173
|
+
this.beginTurn();
|
|
174
|
+
try {
|
|
175
|
+
if (typeof cb === "function") cb();
|
|
176
|
+
if (this.qSize > 0 && this.canStartOperation) {
|
|
177
|
+
if (this.pumping) this.needsPump = true;
|
|
178
|
+
else this.pump();
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this.endRunWithError(error);
|
|
182
|
+
} finally {
|
|
183
|
+
this.endTurn();
|
|
158
184
|
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
185
|
+
},
|
|
186
|
+
(err) => {
|
|
187
|
+
this.activeOperations--;
|
|
188
|
+
this.removeActiveTask(task);
|
|
189
|
+
this.beginTurn();
|
|
190
|
+
try {
|
|
191
|
+
if (this.qSize > 0 && this.canStartOperation) {
|
|
192
|
+
if (this.pumping) this.needsPump = true;
|
|
193
|
+
else this.pump();
|
|
194
|
+
}
|
|
195
|
+
} finally {
|
|
196
|
+
this.endTurn();
|
|
171
197
|
}
|
|
172
|
-
|
|
173
|
-
this.endTurn();
|
|
198
|
+
throw err;
|
|
174
199
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
200
|
+
);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
this.activeOperations--;
|
|
204
|
+
if (typeof ret === "function") ret();
|
|
179
205
|
}
|
|
180
|
-
|
|
181
|
-
|
|
206
|
+
} catch (error) {
|
|
207
|
+
this.endRunWithError(error);
|
|
208
|
+
throw error;
|
|
209
|
+
} finally {
|
|
210
|
+
this.pumping = false;
|
|
182
211
|
}
|
|
183
|
-
this.pumping = false;
|
|
184
212
|
if (this.needsPump) {
|
|
185
213
|
this.needsPump = false;
|
|
186
214
|
this.pump();
|
|
@@ -222,14 +250,30 @@ var CNSStimulation = class {
|
|
|
222
250
|
}
|
|
223
251
|
}
|
|
224
252
|
/**
|
|
225
|
-
* Activations that exist (or are reserved) but whose dendrite
|
|
226
|
-
* been invoked yet: sitting in the ring buffer, created and held in
|
|
253
|
+
* Activations that exist (or are reserved) but whose dendrite response has
|
|
254
|
+
* not been invoked yet: sitting in the ring buffer, created and held in
|
|
227
255
|
* pendingTasks, or reserved by an array fan-out mid-dispatch. Subscribers
|
|
228
256
|
* parked behind an async onResponse land here too - their body has not run.
|
|
229
257
|
*/
|
|
230
258
|
get pendingActivations() {
|
|
231
259
|
return this.qSize + this.scheduledCount + this.reservedActivations;
|
|
232
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* End the run because the machinery itself threw. Dendrite bodies never get
|
|
263
|
+
* here - runStarter catches those and records them as failed tasks; what
|
|
264
|
+
* lands here is a throw from dispatch, today the hop-limit guard in
|
|
265
|
+
* createSubscriberQueueItem. Nothing settles the counters after such a
|
|
266
|
+
* throw, so completion has to be forced: without it the stimulation neither
|
|
267
|
+
* resolves nor rejects, and a worker holds a job that never finishes and
|
|
268
|
+
* never fails.
|
|
269
|
+
*/
|
|
270
|
+
endRunWithError(error) {
|
|
271
|
+
if (this.isCompleted) return;
|
|
272
|
+
this.isCompleted = true;
|
|
273
|
+
this.finalizeReject(
|
|
274
|
+
error instanceof Error ? error : new Error(String(error))
|
|
275
|
+
);
|
|
276
|
+
}
|
|
233
277
|
tryResolveCompleted() {
|
|
234
278
|
if (this.isCompleted) return;
|
|
235
279
|
const noActive = this.activeOperations === 0;
|
|
@@ -253,6 +297,7 @@ var CNSStimulation = class {
|
|
|
253
297
|
}
|
|
254
298
|
}
|
|
255
299
|
this.isCompleted = true;
|
|
300
|
+
if (!noPending) this.emitAbortedResponse();
|
|
256
301
|
this.finalizeReject(new Error("Stimulation aborted"));
|
|
257
302
|
return;
|
|
258
303
|
}
|
|
@@ -285,10 +330,34 @@ var CNSStimulation = class {
|
|
|
285
330
|
*/
|
|
286
331
|
getAllActivationTasks() {
|
|
287
332
|
const queuedTasks = this.qGetQueuedTasks();
|
|
288
|
-
const activeTasks = this.activeTasks
|
|
289
|
-
const pendingTasks = this.pendingTasks
|
|
333
|
+
const activeTasks = this.activeTasks ?? EMPTY_TASKS;
|
|
334
|
+
const pendingTasks = this.pendingTasks ?? EMPTY_TASKS;
|
|
290
335
|
return [...queuedTasks, ...activeTasks, ...pendingTasks];
|
|
291
336
|
}
|
|
337
|
+
getSubscribers(collateral) {
|
|
338
|
+
return this.subscribersByCollateralIndex[collateral.index] ?? EMPTY_SUBSCRIBERS;
|
|
339
|
+
}
|
|
340
|
+
addPositionedTask(tasks, task, positionKey) {
|
|
341
|
+
task[positionKey] = tasks.length;
|
|
342
|
+
tasks.push(task);
|
|
343
|
+
}
|
|
344
|
+
removePositionedTask(tasks, task, positionKey) {
|
|
345
|
+
if (tasks === void 0) return;
|
|
346
|
+
const position = task[positionKey];
|
|
347
|
+
if (position === void 0 || tasks[position] !== task) return;
|
|
348
|
+
const lastTask = tasks.pop();
|
|
349
|
+
if (lastTask !== task) {
|
|
350
|
+
tasks[position] = lastTask;
|
|
351
|
+
lastTask[positionKey] = position;
|
|
352
|
+
}
|
|
353
|
+
task[positionKey] = void 0;
|
|
354
|
+
}
|
|
355
|
+
removeActiveTask(task) {
|
|
356
|
+
this.removePositionedTask(this.activeTasks, task, TASK_ACTIVE_POSITION);
|
|
357
|
+
}
|
|
358
|
+
removePendingTask(task) {
|
|
359
|
+
this.removePositionedTask(this.pendingTasks, task, TASK_PENDING_POSITION);
|
|
360
|
+
}
|
|
292
361
|
/**
|
|
293
362
|
* Returns all tasks that failed or were aborted
|
|
294
363
|
*/
|
|
@@ -301,12 +370,25 @@ var CNSStimulation = class {
|
|
|
301
370
|
* failed/aborted tasks. This is the set to persist for resume: re-activating
|
|
302
371
|
* it re-runs exactly the not-yet-done work. Safe to call at any point during
|
|
303
372
|
* a run (for progress) or after it fails.
|
|
373
|
+
*
|
|
374
|
+
* Each task appears exactly once. The two sources overlap: aborting marks
|
|
375
|
+
* every still-queued task as failed without dequeuing it, so a task sits in
|
|
376
|
+
* the ring buffer AND in failedTasks. Concatenating them blindly handed the
|
|
377
|
+
* broker a checkpoint with the whole queued frontier listed twice, and the
|
|
378
|
+
* resumed run re-executed every one of those tasks a second time - duplicate
|
|
379
|
+
* charges, emails and POSTs on every graceful shutdown.
|
|
304
380
|
*/
|
|
305
381
|
getOutstandingTasks() {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
382
|
+
const seen = /* @__PURE__ */ new Set();
|
|
383
|
+
const outstanding = [];
|
|
384
|
+
const add = (task) => {
|
|
385
|
+
if (seen.has(task)) return;
|
|
386
|
+
seen.add(task);
|
|
387
|
+
outstanding.push(task);
|
|
388
|
+
};
|
|
389
|
+
for (const task of this.getAllActivationTasks()) add(task);
|
|
390
|
+
for (const failure of this.getFailedTasks()) add(failure.task);
|
|
391
|
+
return outstanding;
|
|
310
392
|
}
|
|
311
393
|
/**
|
|
312
394
|
* Enqueue activation tasks directly into the stimulation queue
|
|
@@ -326,17 +408,16 @@ var CNSStimulation = class {
|
|
|
326
408
|
return this.options?.concurrency !== void 0 && this.options?.concurrency > 0;
|
|
327
409
|
}
|
|
328
410
|
createSubscriberQueueItem(subscriber, inputSignal) {
|
|
329
|
-
|
|
330
|
-
|
|
411
|
+
const neuronVisitCounts = this.neuronVisitCounts;
|
|
412
|
+
if (neuronVisitCounts !== void 0) {
|
|
413
|
+
const neuronIndex = subscriber.neuron.index;
|
|
414
|
+
const neuronVisitCount = neuronVisitCounts[neuronIndex] ?? 0;
|
|
415
|
+
if (neuronVisitCount >= this.options.maxNeuronHops) {
|
|
331
416
|
throw new Error(
|
|
332
417
|
`Max neuron hops reached when trying to enqueue subscriber`
|
|
333
418
|
);
|
|
334
|
-
} else {
|
|
335
|
-
this.neuronVisitMap?.set(
|
|
336
|
-
subscriber.neuron,
|
|
337
|
-
(this.neuronVisitMap?.get(subscriber.neuron) ?? 0) + 1
|
|
338
|
-
);
|
|
339
419
|
}
|
|
420
|
+
neuronVisitCounts[neuronIndex] = neuronVisitCount + 1;
|
|
340
421
|
}
|
|
341
422
|
const neuronActivationTask = {
|
|
342
423
|
neuron: subscriber.neuron,
|
|
@@ -344,12 +425,14 @@ var CNSStimulation = class {
|
|
|
344
425
|
input: inputSignal
|
|
345
426
|
};
|
|
346
427
|
neuronActivationTask[TASK_DENDRITE] = subscriber;
|
|
428
|
+
neuronActivationTask[TASK_ACTIVE_POSITION] = void 0;
|
|
429
|
+
neuronActivationTask[TASK_PENDING_POSITION] = void 0;
|
|
347
430
|
return neuronActivationTask;
|
|
348
431
|
}
|
|
349
432
|
executeActivationTask(neuronActivationTask) {
|
|
350
433
|
let subscriber = neuronActivationTask[TASK_DENDRITE];
|
|
351
434
|
if (!subscriber) {
|
|
352
|
-
const subscribers = this.
|
|
435
|
+
const subscribers = this.getSubscribers(
|
|
353
436
|
neuronActivationTask.dendriteCollateral
|
|
354
437
|
);
|
|
355
438
|
for (let i = 0; i < subscribers.length; i++) {
|
|
@@ -371,42 +454,46 @@ var CNSStimulation = class {
|
|
|
371
454
|
void 0,
|
|
372
455
|
neuronActivationTask.input,
|
|
373
456
|
void 0,
|
|
374
|
-
error
|
|
457
|
+
error,
|
|
458
|
+
void 0,
|
|
459
|
+
neuronActivationTask.activationContext
|
|
375
460
|
);
|
|
376
461
|
};
|
|
377
462
|
}
|
|
378
|
-
const
|
|
463
|
+
const neuron = subscriber.neuron;
|
|
379
464
|
const dendrite = subscriber.dendrite;
|
|
380
465
|
const inputSignal = neuronActivationTask.input;
|
|
381
|
-
const concurrency =
|
|
466
|
+
const concurrency = neuron.concurrency;
|
|
382
467
|
if (concurrency === void 0 || concurrency <= 0) {
|
|
383
468
|
return this.runStarter(
|
|
384
|
-
|
|
469
|
+
neuron,
|
|
385
470
|
dendrite,
|
|
386
471
|
inputSignal,
|
|
387
472
|
neuronActivationTask
|
|
388
473
|
);
|
|
389
474
|
}
|
|
390
475
|
return this.instanceNeuronQueue.run(
|
|
391
|
-
|
|
476
|
+
neuron,
|
|
392
477
|
() => this.runStarter(
|
|
393
|
-
|
|
478
|
+
neuron,
|
|
394
479
|
dendrite,
|
|
395
480
|
inputSignal,
|
|
396
481
|
neuronActivationTask
|
|
397
482
|
)
|
|
398
483
|
);
|
|
399
484
|
}
|
|
400
|
-
runStarter(
|
|
485
|
+
runStarter(neuron, dendrite, inputSignal, neuronActivationTask) {
|
|
401
486
|
let response;
|
|
487
|
+
const startedAt = this.onResponse === void 0 ? void 0 : Date.now();
|
|
402
488
|
try {
|
|
403
489
|
response = dendrite.response(
|
|
404
490
|
inputSignal?.payload,
|
|
405
|
-
|
|
491
|
+
neuron.axon,
|
|
406
492
|
new CNSDendriteContext(
|
|
407
493
|
this,
|
|
408
494
|
this.options?.abortSignal,
|
|
409
|
-
this.cns
|
|
495
|
+
this.cns,
|
|
496
|
+
neuronActivationTask
|
|
410
497
|
)
|
|
411
498
|
);
|
|
412
499
|
} catch (error) {
|
|
@@ -418,14 +505,16 @@ var CNSStimulation = class {
|
|
|
418
505
|
});
|
|
419
506
|
return () => {
|
|
420
507
|
this.processResponseOrResponses(
|
|
421
|
-
|
|
508
|
+
neuron,
|
|
422
509
|
inputSignal,
|
|
423
510
|
void 0,
|
|
424
|
-
error
|
|
511
|
+
error,
|
|
512
|
+
startedAt,
|
|
513
|
+
neuronActivationTask.activationContext
|
|
425
514
|
);
|
|
426
515
|
};
|
|
427
516
|
}
|
|
428
|
-
const maxDuration =
|
|
517
|
+
const maxDuration = neuron.maxDuration;
|
|
429
518
|
if (response instanceof Promise || maxDuration) {
|
|
430
519
|
const asPromise = response instanceof Promise ? response : Promise.resolve(response);
|
|
431
520
|
const timedPromise = maxDuration && maxDuration > 0 ? new Promise((resolve, reject) => {
|
|
@@ -450,9 +539,12 @@ var CNSStimulation = class {
|
|
|
450
539
|
(signal) => {
|
|
451
540
|
return () => {
|
|
452
541
|
return this.processResponseOrResponses(
|
|
453
|
-
|
|
542
|
+
neuron,
|
|
454
543
|
inputSignal,
|
|
455
|
-
signal
|
|
544
|
+
signal,
|
|
545
|
+
void 0,
|
|
546
|
+
startedAt,
|
|
547
|
+
neuronActivationTask.activationContext
|
|
456
548
|
);
|
|
457
549
|
};
|
|
458
550
|
},
|
|
@@ -465,10 +557,12 @@ var CNSStimulation = class {
|
|
|
465
557
|
aborted: isAborted
|
|
466
558
|
});
|
|
467
559
|
return this.processResponseOrResponses(
|
|
468
|
-
|
|
560
|
+
neuron,
|
|
469
561
|
inputSignal,
|
|
470
562
|
void 0,
|
|
471
|
-
error
|
|
563
|
+
error,
|
|
564
|
+
startedAt,
|
|
565
|
+
neuronActivationTask.activationContext
|
|
472
566
|
);
|
|
473
567
|
};
|
|
474
568
|
}
|
|
@@ -476,17 +570,29 @@ var CNSStimulation = class {
|
|
|
476
570
|
} else {
|
|
477
571
|
return () => {
|
|
478
572
|
return this.processResponseOrResponses(
|
|
479
|
-
|
|
573
|
+
neuron,
|
|
480
574
|
inputSignal,
|
|
481
|
-
response
|
|
575
|
+
response,
|
|
576
|
+
void 0,
|
|
577
|
+
startedAt,
|
|
578
|
+
neuronActivationTask.activationContext
|
|
482
579
|
);
|
|
483
580
|
};
|
|
484
581
|
}
|
|
485
582
|
}
|
|
486
|
-
processResponseOrResponses(emitter, inputSignal, outputSignalOrSignals, error) {
|
|
583
|
+
processResponseOrResponses(emitter, inputSignal, outputSignalOrSignals, error, startedAt, activationContext) {
|
|
584
|
+
const duration = startedAt === void 0 ? void 0 : Date.now() - startedAt;
|
|
487
585
|
if (Array.isArray(outputSignalOrSignals)) {
|
|
488
586
|
if (outputSignalOrSignals.length === 0) {
|
|
489
|
-
this.processResponse(
|
|
587
|
+
this.processResponse(
|
|
588
|
+
emitter,
|
|
589
|
+
inputSignal,
|
|
590
|
+
void 0,
|
|
591
|
+
error,
|
|
592
|
+
startedAt,
|
|
593
|
+
duration,
|
|
594
|
+
activationContext
|
|
595
|
+
);
|
|
490
596
|
return;
|
|
491
597
|
}
|
|
492
598
|
let perSignal;
|
|
@@ -495,31 +601,82 @@ var CNSStimulation = class {
|
|
|
495
601
|
let reserved = 0;
|
|
496
602
|
for (let i = 0; i < outputSignalOrSignals.length; i++) {
|
|
497
603
|
const c = outputSignalOrSignals[i]?.collateral;
|
|
498
|
-
const n = c ? this.
|
|
604
|
+
const n = c ? this.getSubscribers(c).length : 0;
|
|
499
605
|
perSignal[i] = n;
|
|
500
606
|
reserved += n;
|
|
501
607
|
}
|
|
502
608
|
this.reservedActivations += reserved;
|
|
503
609
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
610
|
+
let dispatchIndex = 0;
|
|
611
|
+
try {
|
|
612
|
+
for (; dispatchIndex < outputSignalOrSignals.length; dispatchIndex++) {
|
|
613
|
+
if (perSignal)
|
|
614
|
+
this.reservedActivations -= perSignal[dispatchIndex];
|
|
615
|
+
this.processResponse(
|
|
616
|
+
emitter,
|
|
617
|
+
inputSignal,
|
|
618
|
+
outputSignalOrSignals[dispatchIndex],
|
|
619
|
+
error,
|
|
620
|
+
startedAt,
|
|
621
|
+
duration,
|
|
622
|
+
activationContext
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
} finally {
|
|
626
|
+
if (perSignal) {
|
|
627
|
+
for (let j = dispatchIndex + 1; j < perSignal.length; j++) {
|
|
628
|
+
this.reservedActivations -= perSignal[j];
|
|
629
|
+
}
|
|
630
|
+
}
|
|
512
631
|
}
|
|
513
632
|
return;
|
|
514
633
|
}
|
|
515
|
-
this.processResponse(
|
|
634
|
+
this.processResponse(
|
|
635
|
+
emitter,
|
|
636
|
+
inputSignal,
|
|
637
|
+
outputSignalOrSignals,
|
|
638
|
+
error,
|
|
639
|
+
startedAt,
|
|
640
|
+
duration,
|
|
641
|
+
activationContext
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* The one response an aborted run emits: no neuron, no signals, an empty
|
|
646
|
+
* queue. It exists so `queueLength === 0` — the completion condition every
|
|
647
|
+
* listener uses — is reached on the abort path too. Listener errors are
|
|
648
|
+
* swallowed: the run is already over and rejecting, and there is nothing
|
|
649
|
+
* left to fail.
|
|
650
|
+
*/
|
|
651
|
+
emitAbortedResponse() {
|
|
652
|
+
const onResponse = this.onResponse;
|
|
653
|
+
if (!onResponse) return;
|
|
654
|
+
try {
|
|
655
|
+
void onResponse(
|
|
656
|
+
new CNSStimulationResponseImpl(
|
|
657
|
+
this,
|
|
658
|
+
void 0,
|
|
659
|
+
void 0,
|
|
660
|
+
0,
|
|
661
|
+
0,
|
|
662
|
+
0,
|
|
663
|
+
void 0,
|
|
664
|
+
new Error("Stimulation aborted"),
|
|
665
|
+
void 0,
|
|
666
|
+
void 0,
|
|
667
|
+
void 0
|
|
668
|
+
)
|
|
669
|
+
);
|
|
670
|
+
} catch (e) {
|
|
671
|
+
this.onResponseError = e instanceof Error ? e : new Error(String(e));
|
|
672
|
+
}
|
|
516
673
|
}
|
|
517
|
-
processResponse(emitter, inputSignal, outputSignal, error) {
|
|
518
|
-
const
|
|
519
|
-
const subscribers =
|
|
674
|
+
processResponse(emitter, inputSignal, outputSignal, error, startedAt, duration, activationContext) {
|
|
675
|
+
const collateral = outputSignal?.collateral;
|
|
676
|
+
const subscribers = collateral ? this.getSubscribers(collateral) : EMPTY_SUBSCRIBERS;
|
|
520
677
|
const onResponse = this.onResponse;
|
|
521
678
|
if (!onResponse) {
|
|
522
|
-
if (
|
|
679
|
+
if (collateral && !error) {
|
|
523
680
|
const n = subscribers.length;
|
|
524
681
|
if (n > 0) {
|
|
525
682
|
const tasks = new Array(n);
|
|
@@ -539,16 +696,17 @@ var CNSStimulation = class {
|
|
|
539
696
|
this.tryResolveCompleted();
|
|
540
697
|
return;
|
|
541
698
|
}
|
|
542
|
-
|
|
543
|
-
if (
|
|
544
|
-
const pending = this.pendingTasks ?? (this.pendingTasks =
|
|
699
|
+
let subscriberActivationTasks = EMPTY_TASKS;
|
|
700
|
+
if (collateral && !error && subscribers.length > 0) {
|
|
701
|
+
const pending = this.pendingTasks ?? (this.pendingTasks = []);
|
|
702
|
+
subscriberActivationTasks = new Array(subscribers.length);
|
|
545
703
|
for (let i = 0; i < subscribers.length; i++) {
|
|
546
704
|
const task = this.createSubscriberQueueItem(
|
|
547
705
|
subscribers[i],
|
|
548
706
|
outputSignal
|
|
549
707
|
);
|
|
550
|
-
subscriberActivationTasks
|
|
551
|
-
|
|
708
|
+
subscriberActivationTasks[i] = task;
|
|
709
|
+
this.addPositionedTask(pending, task, TASK_PENDING_POSITION);
|
|
552
710
|
}
|
|
553
711
|
}
|
|
554
712
|
this.scheduledCount += subscriberActivationTasks.length;
|
|
@@ -562,16 +720,20 @@ var CNSStimulation = class {
|
|
|
562
720
|
this.pendingActivations + this.activeOperations,
|
|
563
721
|
this.pendingActivations,
|
|
564
722
|
this.activeOperations,
|
|
565
|
-
this.options?.maxNeuronHops && emitter ? this.
|
|
723
|
+
this.options?.maxNeuronHops && emitter ? this.neuronVisitCounts?.[emitter.index] ?? 0 : void 0,
|
|
566
724
|
error,
|
|
567
|
-
emitter
|
|
725
|
+
emitter,
|
|
726
|
+
startedAt,
|
|
727
|
+
duration,
|
|
728
|
+
subscriberActivationTasks,
|
|
729
|
+
activationContext
|
|
568
730
|
)
|
|
569
731
|
);
|
|
570
732
|
} catch (e) {
|
|
571
733
|
this.onResponseError = e instanceof Error ? e : new Error(String(e));
|
|
572
734
|
for (let i = 0; i < subscriberActivationTasks.length; i++) {
|
|
573
735
|
this.scheduledCount--;
|
|
574
|
-
this.
|
|
736
|
+
this.removePendingTask(subscriberActivationTasks[i]);
|
|
575
737
|
this.qEnqueue(subscriberActivationTasks[i]);
|
|
576
738
|
}
|
|
577
739
|
this.tryResolveCompleted();
|
|
@@ -584,7 +746,7 @@ var CNSStimulation = class {
|
|
|
584
746
|
try {
|
|
585
747
|
for (let i = 0; i < subscriberActivationTasks.length; i++) {
|
|
586
748
|
this.scheduledCount--;
|
|
587
|
-
this.
|
|
749
|
+
this.removePendingTask(
|
|
588
750
|
subscriberActivationTasks[i]
|
|
589
751
|
);
|
|
590
752
|
this.qEnqueue(subscriberActivationTasks[i]);
|
|
@@ -600,7 +762,7 @@ var CNSStimulation = class {
|
|
|
600
762
|
try {
|
|
601
763
|
for (let i = 0; i < subscriberActivationTasks.length; i++) {
|
|
602
764
|
this.scheduledCount--;
|
|
603
|
-
this.
|
|
765
|
+
this.removePendingTask(
|
|
604
766
|
subscriberActivationTasks[i]
|
|
605
767
|
);
|
|
606
768
|
this.qEnqueue(subscriberActivationTasks[i]);
|
|
@@ -615,7 +777,7 @@ var CNSStimulation = class {
|
|
|
615
777
|
}
|
|
616
778
|
for (let i = 0; i < subscriberActivationTasks.length; i++) {
|
|
617
779
|
this.scheduledCount--;
|
|
618
|
-
this.
|
|
780
|
+
this.removePendingTask(subscriberActivationTasks[i]);
|
|
619
781
|
this.qEnqueue(subscriberActivationTasks[i]);
|
|
620
782
|
}
|
|
621
783
|
this.tryResolveCompleted();
|
|
@@ -642,36 +804,36 @@ var CNSStimulation = class {
|
|
|
642
804
|
// src/CNSInstanceNeuronQueue.ts
|
|
643
805
|
var CNSInstanceNeuronQueue = class {
|
|
644
806
|
constructor() {
|
|
645
|
-
this.
|
|
807
|
+
this.gatesByNeuronIndex = [];
|
|
646
808
|
}
|
|
647
|
-
run(
|
|
648
|
-
const limit =
|
|
809
|
+
run(neuron, fn) {
|
|
810
|
+
const limit = neuron?.concurrency;
|
|
649
811
|
if (limit === void 0 || limit <= 0) {
|
|
650
812
|
return fn();
|
|
651
813
|
}
|
|
652
|
-
let gate = this.
|
|
814
|
+
let gate = this.gatesByNeuronIndex[neuron.index];
|
|
653
815
|
if (!gate) {
|
|
654
816
|
gate = { limit, active: 0, waiters: [] };
|
|
655
|
-
this.
|
|
817
|
+
this.gatesByNeuronIndex[neuron.index] = gate;
|
|
656
818
|
} else {
|
|
657
819
|
gate.limit = limit;
|
|
658
820
|
}
|
|
821
|
+
const ownGate = gate;
|
|
659
822
|
const wrap = (cb) => {
|
|
660
823
|
return () => {
|
|
661
824
|
try {
|
|
662
825
|
cb();
|
|
663
826
|
} finally {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const next = g.waiters.shift();
|
|
827
|
+
ownGate.active = Math.max(0, ownGate.active - 1);
|
|
828
|
+
if (ownGate.waiters.length > 0) {
|
|
829
|
+
const next = ownGate.waiters.shift();
|
|
668
830
|
next();
|
|
669
831
|
}
|
|
670
832
|
}
|
|
671
833
|
};
|
|
672
834
|
};
|
|
673
|
-
if (
|
|
674
|
-
|
|
835
|
+
if (ownGate.active < ownGate.limit) {
|
|
836
|
+
ownGate.active++;
|
|
675
837
|
const ret = fn();
|
|
676
838
|
if (ret && typeof ret.then === "function") {
|
|
677
839
|
return ret.then((cb) => wrap(cb));
|
|
@@ -680,8 +842,7 @@ var CNSInstanceNeuronQueue = class {
|
|
|
680
842
|
}
|
|
681
843
|
return new Promise((resolve) => {
|
|
682
844
|
const starter = () => {
|
|
683
|
-
|
|
684
|
-
g.active++;
|
|
845
|
+
ownGate.active++;
|
|
685
846
|
const ret = fn();
|
|
686
847
|
if (ret && typeof ret.then === "function") {
|
|
687
848
|
ret.then((cb) => resolve(wrap(cb)));
|
|
@@ -689,12 +850,52 @@ var CNSInstanceNeuronQueue = class {
|
|
|
689
850
|
resolve(wrap(ret));
|
|
690
851
|
}
|
|
691
852
|
};
|
|
692
|
-
|
|
853
|
+
ownGate.waiters.push(starter);
|
|
693
854
|
});
|
|
694
855
|
}
|
|
695
856
|
};
|
|
696
857
|
|
|
858
|
+
// src/utils/cnsResolveModalityHandler.ts
|
|
859
|
+
function cnsResolveModalityHandler(modalitySelection, modality, afferentPath) {
|
|
860
|
+
if (modality !== void 0) {
|
|
861
|
+
const modalityConfigs = modalitySelection.modalityConfigs;
|
|
862
|
+
for (let i = 0; i < modalityConfigs.length; i++) {
|
|
863
|
+
const modalityConfig = modalityConfigs[i];
|
|
864
|
+
if (modalityConfig.modality !== modality) continue;
|
|
865
|
+
for (let path = afferentPath; path !== void 0; path = path.parentAfferentPath) {
|
|
866
|
+
const handler = modalityConfig.afferentPaths?.get(path);
|
|
867
|
+
if (handler !== void 0) return handler;
|
|
868
|
+
}
|
|
869
|
+
if (modalityConfig.default !== void 0) return modalityConfig.default;
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return modalitySelection.defaultHandler;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// src/utils/cnsComposeModalityResponse.ts
|
|
877
|
+
function cnsComposeModalityResponse(handler, output) {
|
|
878
|
+
return (payload, axon, ctx) => {
|
|
879
|
+
const maybeResult = handler(payload, axon, ctx);
|
|
880
|
+
if (maybeResult && typeof maybeResult.then === "function") {
|
|
881
|
+
return maybeResult.then((result) => output(result, axon, ctx));
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
884
|
+
return output(maybeResult, axon, ctx);
|
|
885
|
+
} catch (error) {
|
|
886
|
+
if (error instanceof Error && error.message.includes("Cannot read properties of undefined")) {
|
|
887
|
+
const axonKeys = axon && typeof axon === "object" ? Object.keys(axon) : "not an object";
|
|
888
|
+
throw new Error(
|
|
889
|
+
`modalityDendrite: axon.output is undefined. Axon type: ${typeof axon}, Axon keys: ${Array.isArray(axonKeys) ? axonKeys.join(", ") : axonKeys}. Make sure the neuron was created with the correct axon structure. Original error: ${error.message}`
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
throw error;
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
|
|
697
897
|
// src/CNSNetwork.ts
|
|
898
|
+
var NO_SUBSCRIBERS = [];
|
|
698
899
|
var CNSNetwork = class {
|
|
699
900
|
constructor(neurons) {
|
|
700
901
|
this.neurons = neurons;
|
|
@@ -724,16 +925,19 @@ var CNSNetwork = class {
|
|
|
724
925
|
* Fast lookup: collateral -> list of (neuron, dendrite) subscribers.
|
|
725
926
|
* Built once at construction time.
|
|
726
927
|
*/
|
|
727
|
-
this.
|
|
928
|
+
this.subscribersByCollateralIndex = [];
|
|
929
|
+
this.allSubscribersByCollateralIndex = [];
|
|
930
|
+
this.modalityCollateralIndices = [];
|
|
931
|
+
this.subscriberTablesByModalityIndex = [];
|
|
728
932
|
this.parentNeuronByCollateral = /* @__PURE__ */ new Map();
|
|
729
933
|
this.buildIndexes();
|
|
730
934
|
}
|
|
731
935
|
get stronglyConnectedComponents() {
|
|
732
|
-
this.
|
|
936
|
+
this.buildSccIndex();
|
|
733
937
|
return this._stronglyConnectedComponents;
|
|
734
938
|
}
|
|
735
939
|
/** Build the SCC analysis the first time anything needs it. */
|
|
736
|
-
|
|
940
|
+
buildSccIndex() {
|
|
737
941
|
if (this.sccBuilt) return;
|
|
738
942
|
this.sccBuilt = true;
|
|
739
943
|
this.buildSCC();
|
|
@@ -743,23 +947,21 @@ var CNSNetwork = class {
|
|
|
743
947
|
*/
|
|
744
948
|
buildNeuronGraph() {
|
|
745
949
|
const graph = /* @__PURE__ */ new Map();
|
|
746
|
-
for (const
|
|
747
|
-
graph.set(
|
|
950
|
+
for (const neuron of this.neurons) {
|
|
951
|
+
graph.set(neuron, /* @__PURE__ */ new Set());
|
|
748
952
|
}
|
|
749
|
-
for (const
|
|
953
|
+
for (const neuron of this.neurons) {
|
|
750
954
|
const reachableNeurons = /* @__PURE__ */ new Set();
|
|
751
|
-
for (const axonKey in
|
|
752
|
-
const
|
|
753
|
-
const subscribers = this.
|
|
754
|
-
collateral2
|
|
755
|
-
);
|
|
955
|
+
for (const axonKey in neuron.axon) {
|
|
956
|
+
const collateral = neuron.axon[axonKey];
|
|
957
|
+
const subscribers = this.allSubscribersByCollateralIndex[collateral.index];
|
|
756
958
|
if (subscribers) {
|
|
757
959
|
for (const { neuron: targetNeuron } of subscribers) {
|
|
758
960
|
reachableNeurons.add(targetNeuron);
|
|
759
961
|
}
|
|
760
962
|
}
|
|
761
963
|
}
|
|
762
|
-
graph.set(
|
|
964
|
+
graph.set(neuron, reachableNeurons);
|
|
763
965
|
}
|
|
764
966
|
return graph;
|
|
765
967
|
}
|
|
@@ -774,41 +976,41 @@ var CNSNetwork = class {
|
|
|
774
976
|
const stack = [];
|
|
775
977
|
const components = [];
|
|
776
978
|
let currentIndex = 0;
|
|
777
|
-
const strongConnect = (
|
|
778
|
-
index.set(
|
|
779
|
-
lowlink.set(
|
|
979
|
+
const strongConnect = (neuron) => {
|
|
980
|
+
index.set(neuron, currentIndex);
|
|
981
|
+
lowlink.set(neuron, currentIndex);
|
|
780
982
|
currentIndex++;
|
|
781
|
-
stack.push(
|
|
782
|
-
onStack.add(
|
|
783
|
-
const neighbors = graph.get(
|
|
983
|
+
stack.push(neuron);
|
|
984
|
+
onStack.add(neuron);
|
|
985
|
+
const neighbors = graph.get(neuron) || /* @__PURE__ */ new Set();
|
|
784
986
|
for (const neighbor of Array.from(neighbors)) {
|
|
785
987
|
if (!index.has(neighbor)) {
|
|
786
988
|
strongConnect(neighbor);
|
|
787
989
|
lowlink.set(
|
|
788
|
-
|
|
789
|
-
Math.min(lowlink.get(
|
|
990
|
+
neuron,
|
|
991
|
+
Math.min(lowlink.get(neuron), lowlink.get(neighbor))
|
|
790
992
|
);
|
|
791
993
|
} else if (onStack.has(neighbor)) {
|
|
792
994
|
lowlink.set(
|
|
793
|
-
|
|
794
|
-
Math.min(lowlink.get(
|
|
995
|
+
neuron,
|
|
996
|
+
Math.min(lowlink.get(neuron), index.get(neighbor))
|
|
795
997
|
);
|
|
796
998
|
}
|
|
797
999
|
}
|
|
798
|
-
if (lowlink.get(
|
|
1000
|
+
if (lowlink.get(neuron) === index.get(neuron)) {
|
|
799
1001
|
const component = /* @__PURE__ */ new Set();
|
|
800
1002
|
let w;
|
|
801
1003
|
do {
|
|
802
1004
|
w = stack.pop();
|
|
803
1005
|
onStack.delete(w);
|
|
804
1006
|
component.add(w);
|
|
805
|
-
} while (w !==
|
|
1007
|
+
} while (w !== neuron);
|
|
806
1008
|
components.push(component);
|
|
807
1009
|
}
|
|
808
1010
|
};
|
|
809
|
-
for (const
|
|
810
|
-
if (!index.has(
|
|
811
|
-
strongConnect(
|
|
1011
|
+
for (const neuron of this.neurons) {
|
|
1012
|
+
if (!index.has(neuron)) {
|
|
1013
|
+
strongConnect(neuron);
|
|
812
1014
|
}
|
|
813
1015
|
}
|
|
814
1016
|
this._stronglyConnectedComponents = components;
|
|
@@ -830,8 +1032,8 @@ var CNSNetwork = class {
|
|
|
830
1032
|
}
|
|
831
1033
|
for (let i = 0; i < this._stronglyConnectedComponents.length; i++) {
|
|
832
1034
|
const scc = this._stronglyConnectedComponents[i];
|
|
833
|
-
for (const
|
|
834
|
-
const neighbors = neuronGraph.get(
|
|
1035
|
+
for (const neuron of Array.from(scc)) {
|
|
1036
|
+
const neighbors = neuronGraph.get(neuron) || /* @__PURE__ */ new Set();
|
|
835
1037
|
for (const neighbor of Array.from(neighbors)) {
|
|
836
1038
|
const neighborSccIndex = this.neuronToSCC.get(neighbor);
|
|
837
1039
|
if (neighborSccIndex !== void 0 && neighborSccIndex !== i) {
|
|
@@ -884,26 +1086,26 @@ var CNSNetwork = class {
|
|
|
884
1086
|
* Returns true if the neuron is in a strongly connected component with more than one member,
|
|
885
1087
|
* or if it's in a single-member SCC that can reach itself.
|
|
886
1088
|
*/
|
|
887
|
-
getSCCSetByNeuron(
|
|
888
|
-
this.
|
|
889
|
-
const sccIndex = this.neuronToSCC.get(
|
|
1089
|
+
getSCCSetByNeuron(neuron) {
|
|
1090
|
+
this.buildSccIndex();
|
|
1091
|
+
const sccIndex = this.neuronToSCC.get(neuron);
|
|
890
1092
|
if (sccIndex === void 0) return;
|
|
891
1093
|
return this._stronglyConnectedComponents[sccIndex];
|
|
892
1094
|
}
|
|
893
1095
|
/**
|
|
894
1096
|
* Get the SCC index for a given neuron
|
|
895
1097
|
*/
|
|
896
|
-
getSccIndexByNeuron(
|
|
897
|
-
this.
|
|
898
|
-
return this.neuronToSCC.get(
|
|
1098
|
+
getSccIndexByNeuron(neuron) {
|
|
1099
|
+
this.buildSccIndex();
|
|
1100
|
+
return this.neuronToSCC.get(neuron);
|
|
899
1101
|
}
|
|
900
1102
|
/**
|
|
901
1103
|
* Check if a neuron can be guaranteed not to be visited again during the current propagation.
|
|
902
1104
|
* This is the core logic for safe context cleanup.
|
|
903
1105
|
*/
|
|
904
|
-
canNeuronBeGuaranteedDone(
|
|
905
|
-
this.
|
|
906
|
-
const sccIndex = this.neuronToSCC.get(
|
|
1106
|
+
canNeuronBeGuaranteedDone(neuron, activeSccCounts) {
|
|
1107
|
+
this.buildSccIndex();
|
|
1108
|
+
const sccIndex = this.neuronToSCC.get(neuron);
|
|
907
1109
|
if (sccIndex === void 0) return true;
|
|
908
1110
|
if (activeSccCounts.get(sccIndex) && activeSccCounts.get(sccIndex) > 0) {
|
|
909
1111
|
return false;
|
|
@@ -917,36 +1119,93 @@ var CNSNetwork = class {
|
|
|
917
1119
|
}
|
|
918
1120
|
return true;
|
|
919
1121
|
}
|
|
1122
|
+
describeNeuron(neuron) {
|
|
1123
|
+
const axonKeys = Object.keys(neuron.axon);
|
|
1124
|
+
return `neuron #${neuron.index} (${axonKeys.length === 0 ? "no axon" : `axon: ${axonKeys.join(", ")}`})`;
|
|
1125
|
+
}
|
|
1126
|
+
describeCollateral(neuron, collateral) {
|
|
1127
|
+
const axonKey = Object.keys(neuron.axon).find((key) => neuron.axon[key] === collateral);
|
|
1128
|
+
return `"${axonKey}" #${collateral.index}`;
|
|
1129
|
+
}
|
|
920
1130
|
buildIndexes() {
|
|
921
|
-
|
|
1131
|
+
var _a;
|
|
1132
|
+
this.allSubscribersByCollateralIndex = [];
|
|
1133
|
+
this.modalityCollateralIndices = [];
|
|
1134
|
+
this.subscriberTablesByModalityIndex = [];
|
|
922
1135
|
this.parentNeuronByCollateral.clear();
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
);
|
|
1136
|
+
const listedNeurons = /* @__PURE__ */ new Set();
|
|
1137
|
+
for (const neuron of this.neurons) {
|
|
1138
|
+
if (listedNeurons.has(neuron)) {
|
|
1139
|
+
throw new Error(`${this.describeNeuron(neuron)} is listed twice; every dendrite of it would run twice`);
|
|
1140
|
+
}
|
|
1141
|
+
listedNeurons.add(neuron);
|
|
1142
|
+
for (const dendrite of neuron.dendrites) {
|
|
1143
|
+
const index = dendrite.collateral.index;
|
|
1144
|
+
const subscribers = (_a = this.allSubscribersByCollateralIndex)[index] ?? (_a[index] = []);
|
|
1145
|
+
subscribers.push({ neuron, dendrite });
|
|
1146
|
+
if (dendrite.modalitySelection !== void 0 && this.modalityCollateralIndices.indexOf(index) < 0) {
|
|
1147
|
+
this.modalityCollateralIndices.push(index);
|
|
1148
|
+
}
|
|
932
1149
|
}
|
|
933
|
-
Object.values(
|
|
1150
|
+
Object.values(neuron.axon).forEach((collateral) => {
|
|
1151
|
+
const owner = this.parentNeuronByCollateral.get(collateral);
|
|
1152
|
+
if (owner !== void 0 && owner !== neuron) {
|
|
1153
|
+
throw new Error(
|
|
1154
|
+
`collateral ${this.describeCollateral(neuron, collateral)} is in the axons of ${this.describeNeuron(owner)} and ${this.describeNeuron(neuron)}; a collateral is owned by one neuron`
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
934
1157
|
this.parentNeuronByCollateral.set(
|
|
935
|
-
|
|
936
|
-
|
|
1158
|
+
collateral,
|
|
1159
|
+
neuron
|
|
937
1160
|
);
|
|
938
1161
|
});
|
|
939
1162
|
}
|
|
1163
|
+
this.subscribersByCollateralIndex = this.buildSubscriberTable(void 0, void 0);
|
|
1164
|
+
}
|
|
1165
|
+
getSubscriberTable(modality, afferentPath) {
|
|
1166
|
+
var _a, _b;
|
|
1167
|
+
if (modality === void 0) return this.subscribersByCollateralIndex;
|
|
1168
|
+
const subscriberTablesByAfferentPathIndex = (_a = this.subscriberTablesByModalityIndex)[_b = modality.index] ?? (_a[_b] = []);
|
|
1169
|
+
const afferentPathSlot = afferentPath === void 0 ? 0 : afferentPath.index + 1;
|
|
1170
|
+
return subscriberTablesByAfferentPathIndex[afferentPathSlot] ?? (subscriberTablesByAfferentPathIndex[afferentPathSlot] = this.buildSubscriberTable(modality, afferentPath));
|
|
1171
|
+
}
|
|
1172
|
+
buildSubscriberTable(modality, afferentPath) {
|
|
1173
|
+
const subscriberTable = this.allSubscribersByCollateralIndex.slice();
|
|
1174
|
+
const modalityCollateralIndices = this.modalityCollateralIndices;
|
|
1175
|
+
for (let i = 0; i < modalityCollateralIndices.length; i++) {
|
|
1176
|
+
const index = modalityCollateralIndices[i];
|
|
1177
|
+
const resolvedSubscribers = [];
|
|
1178
|
+
const subscribers = this.allSubscribersByCollateralIndex[index];
|
|
1179
|
+
for (let j = 0; j < subscribers.length; j++) {
|
|
1180
|
+
const subscriber = subscribers[j];
|
|
1181
|
+
const modalitySelection = subscriber.dendrite.modalitySelection;
|
|
1182
|
+
if (modalitySelection === void 0) {
|
|
1183
|
+
resolvedSubscribers.push(subscriber);
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
const handler = cnsResolveModalityHandler(modalitySelection, modality, afferentPath);
|
|
1187
|
+
if (handler === void 0) continue;
|
|
1188
|
+
resolvedSubscribers.push({
|
|
1189
|
+
neuron: subscriber.neuron,
|
|
1190
|
+
dendrite: {
|
|
1191
|
+
collateral: subscriber.dendrite.collateral,
|
|
1192
|
+
response: cnsComposeModalityResponse(handler, modalitySelection.output)
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
subscriberTable[index] = resolvedSubscribers;
|
|
1197
|
+
}
|
|
1198
|
+
return subscriberTable;
|
|
940
1199
|
}
|
|
941
|
-
getParentNeuronByCollateral(
|
|
942
|
-
return this.parentNeuronByCollateral.get(
|
|
1200
|
+
getParentNeuronByCollateral(collateral) {
|
|
1201
|
+
return this.parentNeuronByCollateral.get(collateral);
|
|
943
1202
|
}
|
|
944
1203
|
// Computed on demand (rarely used): one dendrite per collateral, last wins —
|
|
945
1204
|
// preserving the previous index semantics without building it every construct.
|
|
946
1205
|
getDendrites() {
|
|
947
1206
|
const byCollateral = /* @__PURE__ */ new Map();
|
|
948
|
-
for (const
|
|
949
|
-
for (const dendrite of
|
|
1207
|
+
for (const neuron of this.neurons) {
|
|
1208
|
+
for (const dendrite of neuron.dendrites) {
|
|
950
1209
|
byCollateral.set(
|
|
951
1210
|
dendrite.collateral,
|
|
952
1211
|
dendrite
|
|
@@ -957,15 +1216,15 @@ var CNSNetwork = class {
|
|
|
957
1216
|
}
|
|
958
1217
|
getCollaterals() {
|
|
959
1218
|
const collaterals = /* @__PURE__ */ new Set();
|
|
960
|
-
for (const
|
|
961
|
-
for (const
|
|
962
|
-
collaterals.add(
|
|
1219
|
+
for (const neuron of this.neurons) {
|
|
1220
|
+
for (const collateral of Object.values(neuron.axon)) {
|
|
1221
|
+
collaterals.add(collateral);
|
|
963
1222
|
}
|
|
964
1223
|
}
|
|
965
1224
|
return Array.from(collaterals);
|
|
966
1225
|
}
|
|
967
|
-
getSubscribers(
|
|
968
|
-
return this.
|
|
1226
|
+
getSubscribers(collateral) {
|
|
1227
|
+
return this.subscribersByCollateralIndex[collateral.index] ?? NO_SUBSCRIBERS;
|
|
969
1228
|
}
|
|
970
1229
|
};
|
|
971
1230
|
|
|
@@ -987,6 +1246,8 @@ var CNS = class {
|
|
|
987
1246
|
* Global batch-boundary listeners applied to every stimulation.
|
|
988
1247
|
*/
|
|
989
1248
|
this.globalDrainListeners = [];
|
|
1249
|
+
this.batchDepth = 0;
|
|
1250
|
+
this.batchedDrains = [];
|
|
990
1251
|
this.network = new CNSNetwork(this.neurons);
|
|
991
1252
|
this.global = global;
|
|
992
1253
|
}
|
|
@@ -1006,7 +1267,7 @@ var CNS = class {
|
|
|
1006
1267
|
}
|
|
1007
1268
|
/**
|
|
1008
1269
|
* Register a listener called once at the end of every synchronous turn of
|
|
1009
|
-
* every stimulation - see {@link TCNSStimulationDrain}. This is how an
|
|
1270
|
+
* every stimulation (inside `batch`, when the batch ends) - see {@link TCNSStimulationDrain}. This is how an
|
|
1010
1271
|
* integration (a state manager, an in-memory database) installs a single
|
|
1011
1272
|
* commit point for the whole organism instead of passing `onDrain` per call.
|
|
1012
1273
|
*
|
|
@@ -1015,6 +1276,18 @@ var CNS = class {
|
|
|
1015
1276
|
addDrainListener(listener) {
|
|
1016
1277
|
return this.addListener(this.globalDrainListeners, listener);
|
|
1017
1278
|
}
|
|
1279
|
+
batch(run) {
|
|
1280
|
+
this.batchDepth++;
|
|
1281
|
+
try {
|
|
1282
|
+
return run();
|
|
1283
|
+
} finally {
|
|
1284
|
+
if (--this.batchDepth === 0) {
|
|
1285
|
+
const batchedDrains = this.batchedDrains;
|
|
1286
|
+
this.batchedDrains = [];
|
|
1287
|
+
for (const [announce, drain] of batchedDrains) announce(drain);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1018
1291
|
/**
|
|
1019
1292
|
* Fan a drain out to the local and global listeners, or return undefined when
|
|
1020
1293
|
* there are none so the stimulation can skip the boundary check entirely.
|
|
@@ -1024,7 +1297,7 @@ var CNS = class {
|
|
|
1024
1297
|
*/
|
|
1025
1298
|
wrapOnDrain(local) {
|
|
1026
1299
|
if (this.globalDrainListeners.length === 0 && !local) return void 0;
|
|
1027
|
-
|
|
1300
|
+
const announce = (d) => {
|
|
1028
1301
|
if (local) {
|
|
1029
1302
|
try {
|
|
1030
1303
|
local(d);
|
|
@@ -1040,39 +1313,38 @@ var CNS = class {
|
|
|
1040
1313
|
}
|
|
1041
1314
|
}
|
|
1042
1315
|
};
|
|
1316
|
+
return (d) => {
|
|
1317
|
+
if (this.batchDepth > 0) this.batchedDrains.push([announce, d]);
|
|
1318
|
+
else announce(d);
|
|
1319
|
+
};
|
|
1043
1320
|
}
|
|
1044
1321
|
wrapOnResponse(local) {
|
|
1045
1322
|
if (this.globalResponseListeners.length === 0 && !local) {
|
|
1046
1323
|
return void 0;
|
|
1047
1324
|
}
|
|
1048
1325
|
return (r) => {
|
|
1049
|
-
let
|
|
1050
|
-
const promises = [];
|
|
1326
|
+
let promises;
|
|
1051
1327
|
if (local) {
|
|
1052
1328
|
try {
|
|
1053
1329
|
const res = local(r);
|
|
1054
1330
|
if (res && typeof res.then === "function") {
|
|
1055
|
-
|
|
1056
|
-
promises.push(res);
|
|
1331
|
+
(promises ?? (promises = [])).push(res);
|
|
1057
1332
|
}
|
|
1058
1333
|
} catch (error) {
|
|
1059
|
-
|
|
1060
|
-
promises.push(Promise.reject(error));
|
|
1334
|
+
(promises ?? (promises = [])).push(Promise.reject(error));
|
|
1061
1335
|
}
|
|
1062
1336
|
}
|
|
1063
1337
|
for (let i = 0; i < this.globalResponseListeners.length; i++) {
|
|
1064
1338
|
try {
|
|
1065
1339
|
const res = this.globalResponseListeners[i](r);
|
|
1066
1340
|
if (res && typeof res.then === "function") {
|
|
1067
|
-
|
|
1068
|
-
promises.push(res);
|
|
1341
|
+
(promises ?? (promises = [])).push(res);
|
|
1069
1342
|
}
|
|
1070
1343
|
} catch (error) {
|
|
1071
|
-
|
|
1072
|
-
promises.push(Promise.reject(error));
|
|
1344
|
+
(promises ?? (promises = [])).push(Promise.reject(error));
|
|
1073
1345
|
}
|
|
1074
1346
|
}
|
|
1075
|
-
if (
|
|
1347
|
+
if (promises) {
|
|
1076
1348
|
return Promise.allSettled(promises).then((results) => {
|
|
1077
1349
|
const rejected = results.find(
|
|
1078
1350
|
(r2) => r2.status === "rejected"
|
|
@@ -1107,8 +1379,20 @@ var CNS = class {
|
|
|
1107
1379
|
}
|
|
1108
1380
|
};
|
|
1109
1381
|
|
|
1382
|
+
// src/createCNS.ts
|
|
1383
|
+
var createCNS = (neuronFactories, dependencies, options) => {
|
|
1384
|
+
if (new Set(neuronFactories).size !== neuronFactories.length) {
|
|
1385
|
+
throw new Error("createCNS: a neuron factory is listed twice; every dendrite of its neuron would run twice");
|
|
1386
|
+
}
|
|
1387
|
+
return new CNS(neuronFactories.map((createNeuron) => createNeuron(dependencies)), options);
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1110
1390
|
// src/CNSCollateral.ts
|
|
1391
|
+
var nextCollateralIndex = 0;
|
|
1111
1392
|
var CNSCollateral = class {
|
|
1393
|
+
constructor() {
|
|
1394
|
+
this.index = nextCollateralIndex++;
|
|
1395
|
+
}
|
|
1112
1396
|
createSignal(payload) {
|
|
1113
1397
|
return { collateral: this, payload };
|
|
1114
1398
|
}
|
|
@@ -1164,8 +1448,11 @@ var CNSStimulationGate = class {
|
|
|
1164
1448
|
};
|
|
1165
1449
|
|
|
1166
1450
|
// src/factory/index.ts
|
|
1167
|
-
var
|
|
1168
|
-
var
|
|
1451
|
+
var nextNeuronIndex = 0;
|
|
1452
|
+
var nextAfferentPathIndex = 0;
|
|
1453
|
+
var nextModalityIndex = 0;
|
|
1454
|
+
var cnsCollateral = () => new CNSCollateral();
|
|
1455
|
+
var cnsNeuron = (axon) => {
|
|
1169
1456
|
const dendrites = [];
|
|
1170
1457
|
const api = {
|
|
1171
1458
|
setConcurrency: (n) => {
|
|
@@ -1189,15 +1476,16 @@ var neuron = (axon) => {
|
|
|
1189
1476
|
}
|
|
1190
1477
|
return api;
|
|
1191
1478
|
}),
|
|
1479
|
+
index: nextNeuronIndex++,
|
|
1192
1480
|
axon,
|
|
1193
1481
|
concurrency: void 0,
|
|
1194
1482
|
maxDuration: void 0,
|
|
1195
1483
|
dendrites,
|
|
1196
1484
|
dendrite(s) {
|
|
1197
1485
|
if ("collateral" in s && "response" in s && Array.isArray(s.collateral)) {
|
|
1198
|
-
for (const
|
|
1486
|
+
for (const collateral of s.collateral) {
|
|
1199
1487
|
dendrites.push({
|
|
1200
|
-
collateral
|
|
1488
|
+
collateral,
|
|
1201
1489
|
response: s.response
|
|
1202
1490
|
});
|
|
1203
1491
|
}
|
|
@@ -1210,7 +1498,7 @@ var neuron = (axon) => {
|
|
|
1210
1498
|
},
|
|
1211
1499
|
modalityDendrite(s) {
|
|
1212
1500
|
const {
|
|
1213
|
-
collateral
|
|
1501
|
+
collateral,
|
|
1214
1502
|
modality: singleModality,
|
|
1215
1503
|
modalities: multipleModalities,
|
|
1216
1504
|
afferentPaths: singleAfferentPaths,
|
|
@@ -1227,83 +1515,52 @@ var neuron = (axon) => {
|
|
|
1227
1515
|
const response = (payload, axon2, ctx) => {
|
|
1228
1516
|
const stimulation = ctx.stimulation;
|
|
1229
1517
|
const stimOptions = stimulation?.options;
|
|
1230
|
-
const
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
);
|
|
1235
|
-
}
|
|
1236
|
-
const res = defaultHandler(payload, axon2, ctx);
|
|
1237
|
-
if (res && typeof res.then === "function") {
|
|
1238
|
-
return res.then(
|
|
1239
|
-
(result) => output(result, axon2, ctx)
|
|
1240
|
-
);
|
|
1241
|
-
}
|
|
1242
|
-
return output(res, axon2, ctx);
|
|
1243
|
-
};
|
|
1244
|
-
if (!stimOptions || !stimOptions.modality) {
|
|
1245
|
-
return runGlobalDefault();
|
|
1246
|
-
}
|
|
1247
|
-
const matchingConfig = modalityConfigs.find(
|
|
1248
|
-
(config) => config.modality === stimOptions.modality
|
|
1518
|
+
const handler = cnsResolveModalityHandler(
|
|
1519
|
+
modalitySelection,
|
|
1520
|
+
stimOptions?.modality,
|
|
1521
|
+
stimOptions?.afferentPath
|
|
1249
1522
|
);
|
|
1250
|
-
if (
|
|
1251
|
-
return runGlobalDefault();
|
|
1252
|
-
}
|
|
1253
|
-
const stimAfferentPath = stimOptions.afferentPath;
|
|
1254
|
-
const handler = stimAfferentPath && matchingConfig.afferentPaths ? matchingConfig.afferentPaths.get(stimAfferentPath) : void 0;
|
|
1255
|
-
const effectiveHandler = handler ?? matchingConfig.default ?? defaultHandler;
|
|
1256
|
-
if (!effectiveHandler) {
|
|
1523
|
+
if (handler === void 0) {
|
|
1257
1524
|
throw new Error(
|
|
1258
1525
|
`modalityDendrite: No handler found for afferent path in modality and no default handler provided`
|
|
1259
1526
|
);
|
|
1260
1527
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
try {
|
|
1268
|
-
return output(maybeResult, axon2, ctx);
|
|
1269
|
-
} catch (error) {
|
|
1270
|
-
if (error instanceof Error && error.message.includes(
|
|
1271
|
-
"Cannot read properties of undefined"
|
|
1272
|
-
)) {
|
|
1273
|
-
const axonKeys = axon2 && typeof axon2 === "object" ? Object.keys(axon2) : "not an object";
|
|
1274
|
-
throw new Error(
|
|
1275
|
-
`modalityDendrite: axon.output is undefined. Axon type: ${typeof axon2}, Axon keys: ${Array.isArray(axonKeys) ? axonKeys.join(", ") : axonKeys}. Make sure the neuron was created with the correct axon structure. Original error: ${error.message}`
|
|
1276
|
-
);
|
|
1277
|
-
}
|
|
1278
|
-
throw error;
|
|
1279
|
-
}
|
|
1528
|
+
return cnsComposeModalityResponse(handler, output)(payload, axon2, ctx);
|
|
1529
|
+
};
|
|
1530
|
+
const modalitySelection = {
|
|
1531
|
+
modalityConfigs,
|
|
1532
|
+
defaultHandler,
|
|
1533
|
+
output
|
|
1280
1534
|
};
|
|
1281
1535
|
dendrites.push({
|
|
1282
|
-
collateral
|
|
1283
|
-
response
|
|
1536
|
+
collateral,
|
|
1537
|
+
response,
|
|
1538
|
+
modalitySelection
|
|
1284
1539
|
});
|
|
1285
1540
|
return api;
|
|
1286
1541
|
}
|
|
1287
1542
|
};
|
|
1288
1543
|
return api;
|
|
1289
1544
|
};
|
|
1290
|
-
var
|
|
1545
|
+
var cnsNeuronFactory = () => {
|
|
1291
1546
|
const factory = {
|
|
1292
1547
|
withGlobal: () => factory,
|
|
1293
|
-
neuron: (axon) =>
|
|
1548
|
+
neuron: (axon) => cnsNeuron(axon)
|
|
1294
1549
|
};
|
|
1295
1550
|
return factory;
|
|
1296
1551
|
};
|
|
1297
|
-
var
|
|
1298
|
-
var
|
|
1552
|
+
var withCNSGlobal = () => cnsNeuronFactory();
|
|
1553
|
+
var cnsAfferentPath = (parentAfferentPath) => {
|
|
1299
1554
|
return {
|
|
1555
|
+
index: nextAfferentPathIndex++,
|
|
1300
1556
|
...parentAfferentPath !== void 0 && {
|
|
1301
1557
|
parentAfferentPath
|
|
1302
1558
|
}
|
|
1303
1559
|
};
|
|
1304
1560
|
};
|
|
1305
|
-
var
|
|
1561
|
+
var cnsModality = (afferentPaths) => {
|
|
1306
1562
|
return {
|
|
1563
|
+
index: nextModalityIndex++,
|
|
1307
1564
|
afferentPaths
|
|
1308
1565
|
};
|
|
1309
1566
|
};
|
|
@@ -1311,11 +1568,12 @@ var modality = (afferentPaths) => {
|
|
|
1311
1568
|
exports.CNS = CNS;
|
|
1312
1569
|
exports.CNSCollateral = CNSCollateral;
|
|
1313
1570
|
exports.CNSStimulationGate = CNSStimulationGate;
|
|
1314
|
-
exports.
|
|
1315
|
-
exports.
|
|
1316
|
-
exports.
|
|
1317
|
-
exports.
|
|
1318
|
-
exports.
|
|
1319
|
-
exports.
|
|
1571
|
+
exports.cnsAfferentPath = cnsAfferentPath;
|
|
1572
|
+
exports.cnsCollateral = cnsCollateral;
|
|
1573
|
+
exports.cnsModality = cnsModality;
|
|
1574
|
+
exports.cnsNeuron = cnsNeuron;
|
|
1575
|
+
exports.cnsNeuronFactory = cnsNeuronFactory;
|
|
1576
|
+
exports.createCNS = createCNS;
|
|
1577
|
+
exports.withCNSGlobal = withCNSGlobal;
|
|
1320
1578
|
//# sourceMappingURL=index.cjs.map
|
|
1321
1579
|
//# sourceMappingURL=index.cjs.map
|