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