@cnstra/core 1.3.1 → 1.5.2
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 +80 -7
- package/dist/CNS.d.ts +43 -9
- package/dist/CNS.d.ts.map +1 -1
- package/dist/CNSCollateral.d.ts +5 -5
- package/dist/CNSCollateral.d.ts.map +1 -1
- package/dist/CNSFunctionalQueue.d.ts +3 -3
- package/dist/CNSFunctionalQueue.d.ts.map +1 -1
- package/dist/CNSInstanceNeuronQueue.d.ts +8 -0
- package/dist/CNSInstanceNeuronQueue.d.ts.map +1 -0
- package/dist/CNSStimulation.d.ts +13 -11
- package/dist/CNSStimulation.d.ts.map +1 -1
- package/dist/CNSTaskQueue.d.ts +12 -0
- package/dist/CNSTaskQueue.d.ts.map +1 -0
- package/dist/factory/index.d.ts +14 -7
- package/dist/factory/index.d.ts.map +1 -1
- package/dist/index.cjs +391 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +391 -85
- package/dist/index.js.map +1 -1
- package/dist/interfaces/ICNS.d.ts +20 -5
- package/dist/interfaces/ICNS.d.ts.map +1 -1
- package/dist/interfaces/ICNSCollateral.d.ts +3 -3
- package/dist/interfaces/ICNSCollateral.d.ts.map +1 -1
- package/dist/types/TCNSAxon.d.ts +3 -1
- package/dist/types/TCNSAxon.d.ts.map +1 -1
- package/dist/types/TCNSDendrite.d.ts +7 -3
- package/dist/types/TCNSDendrite.d.ts.map +1 -1
- package/dist/types/TCNSLocalContextValueStore.d.ts +1 -0
- package/dist/types/TCNSLocalContextValueStore.d.ts.map +1 -1
- package/dist/types/TCNSNeuron.d.ts +5 -3
- package/dist/types/TCNSNeuron.d.ts.map +1 -1
- package/dist/types/TCNSNeuronResponseReturn.d.ts +1 -1
- package/dist/types/TCNSNeuronResponseReturn.d.ts.map +1 -1
- package/dist/types/TCNSSignal.d.ts +2 -2
- package/dist/types/TCNSSignal.d.ts.map +1 -1
- package/dist/types/TCNSStimulationOptions.d.ts +3 -3
- package/dist/types/TCNSStimulationOptions.d.ts.map +1 -1
- package/dist/types/TCNSStimulationQueueItem.d.ts +3 -3
- package/dist/types/TCNSStimulationQueueItem.d.ts.map +1 -1
- package/dist/types/TCNSStimulationResponse.d.ts +3 -3
- package/dist/types/TCNSStimulationResponse.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -132,7 +132,7 @@ var CNSFunctionalQueue = class {
|
|
|
132
132
|
|
|
133
133
|
// src/CNSStimulation.ts
|
|
134
134
|
var CNSStimulation = class {
|
|
135
|
-
constructor(cns, options) {
|
|
135
|
+
constructor(cns, instanceNeuronQueue, options) {
|
|
136
136
|
this.cns = cns;
|
|
137
137
|
this.options = options;
|
|
138
138
|
this.scheduledCount = 0;
|
|
@@ -147,6 +147,7 @@ var CNSStimulation = class {
|
|
|
147
147
|
options?.concurrency,
|
|
148
148
|
options?.abortSignal
|
|
149
149
|
);
|
|
150
|
+
this.instanceNeuronQueue = instanceNeuronQueue;
|
|
150
151
|
if (this.options?.maxNeuronHops) {
|
|
151
152
|
this.nueronVisitMap = /* @__PURE__ */ new Map();
|
|
152
153
|
}
|
|
@@ -175,7 +176,7 @@ var CNSStimulation = class {
|
|
|
175
176
|
* Increment the active count for the SCC containing this neuron
|
|
176
177
|
*/
|
|
177
178
|
incrementSccCount(neuronId) {
|
|
178
|
-
const sccIndex = this.cns.
|
|
179
|
+
const sccIndex = this.cns.getSccIndexByNeuronName(neuronId);
|
|
179
180
|
if (sccIndex === void 0) return;
|
|
180
181
|
const currentCount = this.activeSccCounts.get(sccIndex) || 0;
|
|
181
182
|
this.activeSccCounts.set(sccIndex, currentCount + 1);
|
|
@@ -184,7 +185,7 @@ var CNSStimulation = class {
|
|
|
184
185
|
* Decrement the active count for the SCC containing this neuron
|
|
185
186
|
*/
|
|
186
187
|
decrementSccCount(neuronId) {
|
|
187
|
-
const sccIndex = this.cns.
|
|
188
|
+
const sccIndex = this.cns.getSccIndexByNeuronName(neuronId);
|
|
188
189
|
if (sccIndex === void 0) return;
|
|
189
190
|
const currentCount = this.activeSccCounts.get(sccIndex) || 0;
|
|
190
191
|
const nextCount = Math.max(0, currentCount - 1);
|
|
@@ -206,25 +207,25 @@ var CNSStimulation = class {
|
|
|
206
207
|
);
|
|
207
208
|
}
|
|
208
209
|
cleanupCtxIfNeeded(neuron2) {
|
|
209
|
-
if (this.autoCleanupContextsEnabled && this.canNeuronBeGuaranteedDone(neuron2.
|
|
210
|
-
this.ctx.delete(neuron2.
|
|
210
|
+
if (this.autoCleanupContextsEnabled && this.canNeuronBeGuaranteedDone(neuron2.name)) {
|
|
211
|
+
this.ctx.delete(neuron2.name);
|
|
211
212
|
}
|
|
212
213
|
}
|
|
213
214
|
createSubscriberQueueItem(subscriber, inputSignal) {
|
|
214
215
|
if (this.options?.maxNeuronHops) {
|
|
215
|
-
if (this.nueronVisitMap?.get(subscriber.neuron.
|
|
216
|
+
if (this.nueronVisitMap?.get(subscriber.neuron.name) ?? 0 >= this.options?.maxNeuronHops) {
|
|
216
217
|
throw new Error(
|
|
217
|
-
`Max neuron hops reached for neuron "${subscriber.neuron.
|
|
218
|
+
`Max neuron hops reached for neuron "${subscriber.neuron.name}" when trying to enqueue subscriber "${subscriber.dendrite.collateral.name}" in stimulation "${this.id}"`
|
|
218
219
|
);
|
|
219
220
|
} else {
|
|
220
221
|
this.nueronVisitMap?.set(
|
|
221
|
-
subscriber.neuron.
|
|
222
|
-
(this.nueronVisitMap?.get(subscriber.neuron.
|
|
222
|
+
subscriber.neuron.name,
|
|
223
|
+
(this.nueronVisitMap?.get(subscriber.neuron.name) ?? 0) + 1
|
|
223
224
|
);
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
return {
|
|
227
|
-
neuronId: subscriber.neuron.
|
|
228
|
+
neuronId: subscriber.neuron.name,
|
|
228
229
|
subscriber,
|
|
229
230
|
inputSignal
|
|
230
231
|
};
|
|
@@ -232,52 +233,57 @@ var CNSStimulation = class {
|
|
|
232
233
|
processQueueItem(item) {
|
|
233
234
|
const { subscriber, inputSignal } = item;
|
|
234
235
|
const { neuron: neuron2, dendrite } = subscriber;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return response.then(
|
|
247
|
-
(signal) => {
|
|
248
|
-
return () => {
|
|
249
|
-
this.markNeuronInactive(neuron2.id);
|
|
250
|
-
return this.processResponse(
|
|
251
|
-
inputSignal,
|
|
252
|
-
signal
|
|
253
|
-
);
|
|
254
|
-
};
|
|
255
|
-
},
|
|
256
|
-
(error) => {
|
|
257
|
-
return () => {
|
|
258
|
-
this.markNeuronInactive(neuron2.id);
|
|
259
|
-
return this.processResponse(
|
|
260
|
-
inputSignal,
|
|
261
|
-
void 0,
|
|
262
|
-
error
|
|
263
|
-
);
|
|
264
|
-
};
|
|
236
|
+
const starter = () => {
|
|
237
|
+
this.markNeuronActive(neuron2.name);
|
|
238
|
+
const response = dendrite.response(
|
|
239
|
+
inputSignal?.payload,
|
|
240
|
+
neuron2.axon,
|
|
241
|
+
{
|
|
242
|
+
get: () => this.ctx.get(neuron2.name),
|
|
243
|
+
set: (value) => this.ctx.set(neuron2.name, value),
|
|
244
|
+
delete: () => this.ctx.delete(neuron2.name),
|
|
245
|
+
abortSignal: this.options?.abortSignal,
|
|
246
|
+
cns: this.cns
|
|
265
247
|
}
|
|
266
248
|
);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
249
|
+
if (response instanceof Promise) {
|
|
250
|
+
return response.then(
|
|
251
|
+
(signal) => {
|
|
252
|
+
return () => {
|
|
253
|
+
this.markNeuronInactive(neuron2.name);
|
|
254
|
+
return this.processResponse(
|
|
255
|
+
inputSignal,
|
|
256
|
+
signal
|
|
257
|
+
);
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
(error) => {
|
|
261
|
+
return () => {
|
|
262
|
+
this.markNeuronInactive(neuron2.name);
|
|
263
|
+
return this.processResponse(
|
|
264
|
+
inputSignal,
|
|
265
|
+
void 0,
|
|
266
|
+
error
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
}
|
|
273
270
|
);
|
|
274
|
-
}
|
|
275
|
-
|
|
271
|
+
} else {
|
|
272
|
+
return () => {
|
|
273
|
+
this.markNeuronInactive(neuron2.name);
|
|
274
|
+
return this.processResponse(
|
|
275
|
+
inputSignal,
|
|
276
|
+
response
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
return this.instanceNeuronQueue.run(neuron2, starter);
|
|
276
282
|
}
|
|
277
283
|
processResponse(inputSignal, outputSignal, error) {
|
|
278
284
|
const collateral2 = outputSignal?.collateral;
|
|
279
|
-
const ownerNeuron = collateral2 ? this.cns.
|
|
280
|
-
const subscribers = collateral2 ? this.cns.getSubscribers(collateral2.
|
|
285
|
+
const ownerNeuron = collateral2 ? this.cns.getParentNeuronByCollateralName(collateral2.name) : void 0;
|
|
286
|
+
const subscribers = collateral2 ? this.cns.getSubscribers(collateral2.name) : [];
|
|
281
287
|
const subscriberQueueItems = [];
|
|
282
288
|
if (collateral2 && !error) {
|
|
283
289
|
if (ownerNeuron) {
|
|
@@ -295,7 +301,8 @@ var CNSStimulation = class {
|
|
|
295
301
|
outputSignal,
|
|
296
302
|
ctx: this.ctx,
|
|
297
303
|
queueLength: this.queue.length + this.scheduledCount,
|
|
298
|
-
|
|
304
|
+
stimulationId: this.id,
|
|
305
|
+
hops: this.options?.maxNeuronHops && ownerNeuron ? this.nueronVisitMap?.get(ownerNeuron.name) ?? 0 : void 0,
|
|
299
306
|
error
|
|
300
307
|
});
|
|
301
308
|
for (let i = 0; i < subscriberQueueItems.length; i++) {
|
|
@@ -304,27 +311,167 @@ var CNSStimulation = class {
|
|
|
304
311
|
}
|
|
305
312
|
}
|
|
306
313
|
responseToSignal(signal) {
|
|
307
|
-
const ownerNeuron = this.cns.
|
|
308
|
-
signal.collateral.
|
|
314
|
+
const ownerNeuron = this.cns.getParentNeuronByCollateralName(
|
|
315
|
+
signal.collateral.name
|
|
309
316
|
);
|
|
310
317
|
if (ownerNeuron) {
|
|
311
|
-
this.markNeuronActive(ownerNeuron.
|
|
318
|
+
this.markNeuronActive(ownerNeuron.name);
|
|
312
319
|
}
|
|
313
320
|
this.processResponse(void 0, signal);
|
|
314
321
|
}
|
|
315
322
|
};
|
|
316
323
|
|
|
324
|
+
// src/CNSTaskQueue.ts
|
|
325
|
+
var CNSTaskQueue = class {
|
|
326
|
+
constructor(concurrency) {
|
|
327
|
+
this.concurrency = concurrency;
|
|
328
|
+
this.pumping = false;
|
|
329
|
+
this.needsPump = false;
|
|
330
|
+
this.activeOperations = 0;
|
|
331
|
+
this.items = [];
|
|
332
|
+
}
|
|
333
|
+
get canStartOperation() {
|
|
334
|
+
const limit = this.concurrency ?? Infinity;
|
|
335
|
+
return this.activeOperations < limit;
|
|
336
|
+
}
|
|
337
|
+
enqueue(task) {
|
|
338
|
+
return new Promise((resolve, reject) => {
|
|
339
|
+
this.items.push({ task, resolve, reject });
|
|
340
|
+
if (!this.pumping) this.pump();
|
|
341
|
+
else this.needsPump = true;
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
pump() {
|
|
345
|
+
if (this.pumping) {
|
|
346
|
+
this.needsPump = true;
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
this.pumping = true;
|
|
350
|
+
while (this.canStartOperation && this.items.length > 0) {
|
|
351
|
+
const { task, resolve, reject } = this.items.shift();
|
|
352
|
+
this.activeOperations++;
|
|
353
|
+
try {
|
|
354
|
+
const ret = task();
|
|
355
|
+
if (ret && typeof ret.then === "function") {
|
|
356
|
+
ret.then(
|
|
357
|
+
(cb) => {
|
|
358
|
+
try {
|
|
359
|
+
if (typeof cb === "function") cb();
|
|
360
|
+
resolve();
|
|
361
|
+
} finally {
|
|
362
|
+
this.activeOperations--;
|
|
363
|
+
if (this.items.length > 0 && this.canStartOperation) {
|
|
364
|
+
if (this.pumping) this.needsPump = true;
|
|
365
|
+
else this.pump();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
(err) => {
|
|
370
|
+
try {
|
|
371
|
+
reject(err);
|
|
372
|
+
} finally {
|
|
373
|
+
this.activeOperations--;
|
|
374
|
+
if (this.items.length > 0 && this.canStartOperation) {
|
|
375
|
+
if (this.pumping) this.needsPump = true;
|
|
376
|
+
else this.pump();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
break;
|
|
382
|
+
} else {
|
|
383
|
+
try {
|
|
384
|
+
if (typeof ret === "function") ret();
|
|
385
|
+
resolve();
|
|
386
|
+
} finally {
|
|
387
|
+
this.activeOperations--;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
} catch (e) {
|
|
391
|
+
try {
|
|
392
|
+
reject(e);
|
|
393
|
+
} finally {
|
|
394
|
+
this.activeOperations--;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
this.pumping = false;
|
|
399
|
+
if (this.needsPump) {
|
|
400
|
+
this.needsPump = false;
|
|
401
|
+
this.pump();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// src/CNSInstanceNeuronQueue.ts
|
|
407
|
+
var CNSInstanceNeuronQueue = class {
|
|
408
|
+
constructor() {
|
|
409
|
+
this.gates = /* @__PURE__ */ new Map();
|
|
410
|
+
}
|
|
411
|
+
run(neuron2, fn) {
|
|
412
|
+
const limit = neuron2?.concurrency;
|
|
413
|
+
if (limit === void 0 || limit <= 0) {
|
|
414
|
+
return fn();
|
|
415
|
+
}
|
|
416
|
+
let gate = this.gates.get(neuron2.name);
|
|
417
|
+
if (!gate) {
|
|
418
|
+
gate = { limit, active: 0, waiters: [] };
|
|
419
|
+
this.gates.set(neuron2.name, gate);
|
|
420
|
+
} else {
|
|
421
|
+
gate.limit = limit;
|
|
422
|
+
}
|
|
423
|
+
const wrap = (cb) => {
|
|
424
|
+
return () => {
|
|
425
|
+
try {
|
|
426
|
+
cb();
|
|
427
|
+
} finally {
|
|
428
|
+
const g = this.gates.get(neuron2.name);
|
|
429
|
+
g.active = Math.max(0, g.active - 1);
|
|
430
|
+
if (g.waiters.length > 0) {
|
|
431
|
+
const next = g.waiters.shift();
|
|
432
|
+
next();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
};
|
|
437
|
+
if (gate.active < gate.limit) {
|
|
438
|
+
gate.active++;
|
|
439
|
+
const ret = fn();
|
|
440
|
+
if (ret && typeof ret.then === "function") {
|
|
441
|
+
return ret.then((cb) => wrap(cb));
|
|
442
|
+
}
|
|
443
|
+
return wrap(ret);
|
|
444
|
+
}
|
|
445
|
+
return new Promise((resolve) => {
|
|
446
|
+
const starter = () => {
|
|
447
|
+
const g = this.gates.get(neuron2.name);
|
|
448
|
+
g.active++;
|
|
449
|
+
const ret = fn();
|
|
450
|
+
if (ret && typeof ret.then === "function") {
|
|
451
|
+
ret.then((cb) => resolve(wrap(cb)));
|
|
452
|
+
} else {
|
|
453
|
+
resolve(wrap(ret));
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
gate.waiters.push(starter);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
317
461
|
// src/CNS.ts
|
|
318
462
|
var CNS = class {
|
|
319
463
|
constructor(neurons, options) {
|
|
320
464
|
this.neurons = neurons;
|
|
321
465
|
this.options = options;
|
|
322
466
|
/**
|
|
323
|
-
* Fast lookup:
|
|
467
|
+
* Fast lookup: collateralName -> list of (neuron, dendrite) subscribers.
|
|
324
468
|
* Built once at construction time.
|
|
325
469
|
*/
|
|
326
470
|
this.subIndex = /* @__PURE__ */ new Map();
|
|
327
|
-
this.
|
|
471
|
+
this.neuronIndex = /* @__PURE__ */ new Map();
|
|
472
|
+
this.dendriteIndex = /* @__PURE__ */ new Map();
|
|
473
|
+
this.collateralIndex = /* @__PURE__ */ new Map();
|
|
474
|
+
this.parentNeuronByCollateralName = /* @__PURE__ */ new Map();
|
|
328
475
|
/**
|
|
329
476
|
* Strongly Connected Components of the neuron graph.
|
|
330
477
|
* Each component is a set of neuron IDs that can reach each other.
|
|
@@ -342,29 +489,157 @@ var CNS = class {
|
|
|
342
489
|
* Precomputed ancestor sets for each SCC for fast reachability checks
|
|
343
490
|
*/
|
|
344
491
|
this.sccAncestors = /* @__PURE__ */ new Map();
|
|
492
|
+
/**
|
|
493
|
+
* Global per-neuron concurrency gates shared across all stimulations.
|
|
494
|
+
*/
|
|
495
|
+
this.neuronGates = /* @__PURE__ */ new Map();
|
|
496
|
+
/**
|
|
497
|
+
* Global task queue used by stimulation to schedule per-neuron gated work.
|
|
498
|
+
* Does not replace per-stimulation concurrency; only coordinates global per-neuron limits.
|
|
499
|
+
*/
|
|
500
|
+
this.globalTaskQueue = new CNSTaskQueue();
|
|
501
|
+
this.instanceNeuronQueue = new CNSInstanceNeuronQueue();
|
|
502
|
+
/**
|
|
503
|
+
* Global response listeners applied to every stimulation.
|
|
504
|
+
*/
|
|
505
|
+
this.globalResponseListeners = [];
|
|
506
|
+
this.validateUniqueIdentifiers();
|
|
345
507
|
this.buildIndexes();
|
|
346
508
|
if (this.options?.autoCleanupContexts) {
|
|
347
509
|
this.buildSCC();
|
|
348
510
|
}
|
|
349
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Validate uniqueness constraints:
|
|
514
|
+
* - neuron names are unique and non-empty
|
|
515
|
+
* - axon collateral types are globally unique (single owner)
|
|
516
|
+
* - the same collateral instance cannot be owned by multiple neurons
|
|
517
|
+
* Throws aggregated error if violations are found.
|
|
518
|
+
*/
|
|
519
|
+
validateUniqueIdentifiers() {
|
|
520
|
+
const errors = [];
|
|
521
|
+
const seenNeuronNames = /* @__PURE__ */ new Set();
|
|
522
|
+
for (const neuron2 of this.neurons) {
|
|
523
|
+
const name = neuron2?.name ?? "";
|
|
524
|
+
if (!name || typeof name !== "string" || name.trim().length === 0) {
|
|
525
|
+
errors.push(`Neuron has empty or invalid name: "${name}"`);
|
|
526
|
+
} else if (seenNeuronNames.has(name)) {
|
|
527
|
+
errors.push(`Duplicate neuron name: "${name}"`);
|
|
528
|
+
} else {
|
|
529
|
+
seenNeuronNames.add(name);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (errors.length > 0) {
|
|
533
|
+
throw new Error(
|
|
534
|
+
`[CNS] Topology uniqueness validation failed:
|
|
535
|
+
- ` + errors.join(`
|
|
536
|
+
- `)
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
addResponseListener(listener) {
|
|
541
|
+
this.globalResponseListeners.push(listener);
|
|
542
|
+
let active = true;
|
|
543
|
+
return () => {
|
|
544
|
+
if (!active) return;
|
|
545
|
+
active = false;
|
|
546
|
+
const idx = this.globalResponseListeners.indexOf(listener);
|
|
547
|
+
if (idx >= 0) this.globalResponseListeners.splice(idx, 1);
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
wrapOnResponse(local) {
|
|
551
|
+
if (this.globalResponseListeners.length === 0 && !local) {
|
|
552
|
+
return () => {
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
return (r) => {
|
|
556
|
+
try {
|
|
557
|
+
if (local) local(r);
|
|
558
|
+
} finally {
|
|
559
|
+
for (let i = 0; i < this.globalResponseListeners.length; i++) {
|
|
560
|
+
try {
|
|
561
|
+
this.globalResponseListeners[i](r);
|
|
562
|
+
} catch (e) {
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
runWithNeuronConcurrency(neuron2, fn) {
|
|
569
|
+
const limit = neuron2.concurrency;
|
|
570
|
+
if (limit === void 0 || limit <= 0) {
|
|
571
|
+
return this.globalTaskQueue.enqueue(fn);
|
|
572
|
+
}
|
|
573
|
+
let gate = this.neuronGates.get(neuron2.name);
|
|
574
|
+
if (!gate) {
|
|
575
|
+
gate = { limit, active: 0, waiters: [] };
|
|
576
|
+
this.neuronGates.set(neuron2.name, gate);
|
|
577
|
+
} else {
|
|
578
|
+
gate.limit = limit;
|
|
579
|
+
}
|
|
580
|
+
const wrap = (cb) => {
|
|
581
|
+
return () => {
|
|
582
|
+
try {
|
|
583
|
+
cb();
|
|
584
|
+
} finally {
|
|
585
|
+
gate.active = Math.max(0, gate.active - 1);
|
|
586
|
+
if (gate.waiters.length > 0) {
|
|
587
|
+
const next = gate.waiters.shift();
|
|
588
|
+
next();
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
};
|
|
593
|
+
if (gate.active < gate.limit) {
|
|
594
|
+
gate.active++;
|
|
595
|
+
return this.globalTaskQueue.enqueue(() => {
|
|
596
|
+
const ret = fn();
|
|
597
|
+
if (ret && typeof ret.then === "function") {
|
|
598
|
+
return ret.then((cb) => wrap(cb));
|
|
599
|
+
}
|
|
600
|
+
return wrap(ret);
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
return new Promise((resolve) => {
|
|
604
|
+
const starter = () => {
|
|
605
|
+
gate.active++;
|
|
606
|
+
this.globalTaskQueue.enqueue(() => {
|
|
607
|
+
const ret = fn();
|
|
608
|
+
if (ret && typeof ret.then === "function") {
|
|
609
|
+
return ret.then(
|
|
610
|
+
(cb) => wrap(cb)
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
return wrap(ret);
|
|
614
|
+
}).then(resolve);
|
|
615
|
+
};
|
|
616
|
+
gate.waiters.push(starter);
|
|
617
|
+
});
|
|
618
|
+
}
|
|
350
619
|
buildIndexes() {
|
|
351
620
|
this.subIndex.clear();
|
|
352
|
-
this.
|
|
621
|
+
this.parentNeuronByCollateralName.clear();
|
|
353
622
|
for (const neuron2 of this.neurons) {
|
|
623
|
+
this.neuronIndex.set(neuron2.name, neuron2);
|
|
354
624
|
for (const dendrite of neuron2.dendrites) {
|
|
355
|
-
const key = dendrite.collateral.
|
|
625
|
+
const key = dendrite.collateral.name;
|
|
356
626
|
const arr = this.subIndex.get(key) ?? [];
|
|
357
627
|
arr.push({ neuron: neuron2, dendrite });
|
|
358
628
|
this.subIndex.set(
|
|
359
629
|
key,
|
|
360
630
|
arr
|
|
361
631
|
);
|
|
632
|
+
this.dendriteIndex.set(key, dendrite);
|
|
362
633
|
}
|
|
363
634
|
Object.values(neuron2.axon).forEach((collateral2) => {
|
|
364
|
-
this.
|
|
365
|
-
collateral2.
|
|
635
|
+
this.parentNeuronByCollateralName.set(
|
|
636
|
+
collateral2.name,
|
|
366
637
|
neuron2
|
|
367
638
|
);
|
|
639
|
+
this.collateralIndex.set(
|
|
640
|
+
collateral2.name,
|
|
641
|
+
collateral2
|
|
642
|
+
);
|
|
368
643
|
});
|
|
369
644
|
}
|
|
370
645
|
}
|
|
@@ -373,7 +648,7 @@ var CNS = class {
|
|
|
373
648
|
*/
|
|
374
649
|
buildNeuronGraph() {
|
|
375
650
|
const graph = /* @__PURE__ */ new Map();
|
|
376
|
-
const neuronIds = this.neurons.map((n) => n.
|
|
651
|
+
const neuronIds = this.neurons.map((n) => n.name);
|
|
377
652
|
for (const neuronId of neuronIds) {
|
|
378
653
|
graph.set(neuronId, /* @__PURE__ */ new Set());
|
|
379
654
|
}
|
|
@@ -381,15 +656,15 @@ var CNS = class {
|
|
|
381
656
|
const reachableNeurons = /* @__PURE__ */ new Set();
|
|
382
657
|
for (const axonKey in neuron2.axon) {
|
|
383
658
|
const collateral2 = neuron2.axon[axonKey];
|
|
384
|
-
const
|
|
385
|
-
const subscribers = this.subIndex.get(
|
|
659
|
+
const collateralName = collateral2.name;
|
|
660
|
+
const subscribers = this.subIndex.get(collateralName);
|
|
386
661
|
if (subscribers) {
|
|
387
662
|
for (const { neuron: targetNeuron } of subscribers) {
|
|
388
|
-
reachableNeurons.add(targetNeuron.
|
|
663
|
+
reachableNeurons.add(targetNeuron.name);
|
|
389
664
|
}
|
|
390
665
|
}
|
|
391
666
|
}
|
|
392
|
-
graph.set(neuron2.
|
|
667
|
+
graph.set(neuron2.name, reachableNeurons);
|
|
393
668
|
}
|
|
394
669
|
return graph;
|
|
395
670
|
}
|
|
@@ -398,7 +673,7 @@ var CNS = class {
|
|
|
398
673
|
*/
|
|
399
674
|
buildSCC() {
|
|
400
675
|
const graph = this.buildNeuronGraph();
|
|
401
|
-
const neuronIds = this.neurons.map((n) => n.
|
|
676
|
+
const neuronIds = this.neurons.map((n) => n.name);
|
|
402
677
|
const index = /* @__PURE__ */ new Map();
|
|
403
678
|
const lowlink = /* @__PURE__ */ new Map();
|
|
404
679
|
const onStack = /* @__PURE__ */ new Set();
|
|
@@ -525,23 +800,23 @@ var CNS = class {
|
|
|
525
800
|
* Returns true if the neuron is in a strongly connected component with more than one member,
|
|
526
801
|
* or if it's in a single-member SCC that can reach itself.
|
|
527
802
|
*/
|
|
528
|
-
|
|
529
|
-
const sccIndex = this.neuronToSCC.get(
|
|
803
|
+
getSCCSetByNeuronName(neuronName) {
|
|
804
|
+
const sccIndex = this.neuronToSCC.get(neuronName);
|
|
530
805
|
if (sccIndex === void 0) return;
|
|
531
806
|
return this.stronglyConnectedComponents[sccIndex];
|
|
532
807
|
}
|
|
533
808
|
/**
|
|
534
809
|
* Get the SCC index for a given neuron ID
|
|
535
810
|
*/
|
|
536
|
-
|
|
537
|
-
return this.neuronToSCC.get(
|
|
811
|
+
getSccIndexByNeuronName(neuronName) {
|
|
812
|
+
return this.neuronToSCC.get(neuronName);
|
|
538
813
|
}
|
|
539
814
|
/**
|
|
540
815
|
* Check if a neuron can be guaranteed not to be visited again during the current propagation.
|
|
541
816
|
* This is the core logic for safe context cleanup.
|
|
542
817
|
*/
|
|
543
|
-
canNeuronBeGuaranteedDone(
|
|
544
|
-
const sccIndex = this.neuronToSCC.get(
|
|
818
|
+
canNeuronBeGuaranteedDone(neuronName, activeSccCounts) {
|
|
819
|
+
const sccIndex = this.neuronToSCC.get(neuronName);
|
|
545
820
|
if (sccIndex === void 0) return true;
|
|
546
821
|
if (activeSccCounts.get(sccIndex) && activeSccCounts.get(sccIndex) > 0) {
|
|
547
822
|
return false;
|
|
@@ -555,22 +830,35 @@ var CNS = class {
|
|
|
555
830
|
}
|
|
556
831
|
return true;
|
|
557
832
|
}
|
|
558
|
-
|
|
559
|
-
return this.
|
|
833
|
+
getParentNeuronByCollateralName(collateralName) {
|
|
834
|
+
return this.parentNeuronByCollateralName.get(collateralName);
|
|
835
|
+
}
|
|
836
|
+
getDendrites() {
|
|
837
|
+
return Array.from(this.dendriteIndex.values());
|
|
560
838
|
}
|
|
561
|
-
|
|
562
|
-
return this.
|
|
839
|
+
getCollaterals() {
|
|
840
|
+
return Array.from(this.collateralIndex.values());
|
|
841
|
+
}
|
|
842
|
+
getNeurons() {
|
|
843
|
+
return Array.from(this.neuronIndex.values());
|
|
844
|
+
}
|
|
845
|
+
getSubscribers(collateralName) {
|
|
846
|
+
return this.subIndex.get(collateralName) ?? [];
|
|
563
847
|
}
|
|
564
848
|
stimulate(signal, options) {
|
|
565
|
-
const
|
|
849
|
+
const wrapped = this.wrapOnResponse(options?.onResponse);
|
|
850
|
+
const stimulation = new CNSStimulation(this, this.instanceNeuronQueue, {
|
|
851
|
+
...options,
|
|
852
|
+
onResponse: wrapped
|
|
853
|
+
});
|
|
566
854
|
stimulation.responseToSignal(signal);
|
|
567
855
|
}
|
|
568
856
|
};
|
|
569
857
|
|
|
570
858
|
// src/CNSCollateral.ts
|
|
571
859
|
var CNSCollateral = class {
|
|
572
|
-
constructor(
|
|
573
|
-
this.
|
|
860
|
+
constructor(name) {
|
|
861
|
+
this.name = name;
|
|
574
862
|
}
|
|
575
863
|
createSignal(payload) {
|
|
576
864
|
return { collateral: this, payload };
|
|
@@ -578,12 +866,30 @@ var CNSCollateral = class {
|
|
|
578
866
|
};
|
|
579
867
|
|
|
580
868
|
// src/factory/index.ts
|
|
581
|
-
var collateral = (
|
|
582
|
-
var neuron = (
|
|
869
|
+
var collateral = (name) => new CNSCollateral(name);
|
|
870
|
+
var neuron = (name, axon) => {
|
|
583
871
|
const dendrites = [];
|
|
584
872
|
const api = {
|
|
873
|
+
setConcurrency: (n) => {
|
|
874
|
+
api.concurrency = n;
|
|
875
|
+
return api;
|
|
876
|
+
},
|
|
877
|
+
bind: ((axon2, newDendrites) => {
|
|
878
|
+
for (const key in newDendrites) {
|
|
879
|
+
const value = newDendrites[key];
|
|
880
|
+
const dendrite = typeof value === "function" ? {
|
|
881
|
+
collateral: axon2[key],
|
|
882
|
+
response: value
|
|
883
|
+
} : value;
|
|
884
|
+
dendrites.push(
|
|
885
|
+
dendrite
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
return api;
|
|
889
|
+
}),
|
|
585
890
|
axon,
|
|
586
|
-
|
|
891
|
+
name,
|
|
892
|
+
concurrency: void 0,
|
|
587
893
|
dendrites,
|
|
588
894
|
dendrite(s) {
|
|
589
895
|
dendrites.push(
|
|
@@ -595,13 +901,14 @@ var neuron = (id, axon) => {
|
|
|
595
901
|
return api;
|
|
596
902
|
};
|
|
597
903
|
var withCtx = () => ({
|
|
598
|
-
neuron: (
|
|
599
|
-
return neuron(
|
|
904
|
+
neuron: (name, axon) => {
|
|
905
|
+
return neuron(name, axon);
|
|
600
906
|
}
|
|
601
907
|
});
|
|
602
908
|
|
|
603
909
|
exports.CNS = CNS;
|
|
604
910
|
exports.CNSCollateral = CNSCollateral;
|
|
911
|
+
exports.CNSStimulationContextStore = CNSStimulationContextStore;
|
|
605
912
|
exports.collateral = collateral;
|
|
606
913
|
exports.neuron = neuron;
|
|
607
914
|
exports.withCtx = withCtx;
|