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