@cnstra/core 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -557
- package/dist/CNS.d.ts +76 -0
- package/dist/CNS.d.ts.map +1 -0
- package/dist/CNSCollateral.d.ts +9 -0
- package/dist/CNSCollateral.d.ts.map +1 -0
- package/dist/CNSFunctionalQueue.d.ts +26 -0
- package/dist/CNSFunctionalQueue.d.ts.map +1 -0
- package/dist/CNSStimulation.d.ts +52 -0
- package/dist/CNSStimulation.d.ts.map +1 -0
- package/dist/CNSStimulationContextStore.d.ts +11 -0
- package/dist/CNSStimulationContextStore.d.ts.map +1 -0
- package/dist/factory/index.d.ts +16 -0
- package/dist/factory/index.d.ts.map +1 -0
- package/dist/index.cjs +490 -214
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +490 -214
- package/dist/index.js.map +1 -1
- package/dist/interfaces/ICNS.d.ts +14 -0
- package/dist/interfaces/ICNS.d.ts.map +1 -0
- package/dist/interfaces/ICNSCollateral.d.ts +6 -0
- package/dist/interfaces/ICNSCollateral.d.ts.map +1 -0
- package/dist/interfaces/ICNSStimulationContextStore.d.ts +8 -0
- package/dist/interfaces/ICNSStimulationContextStore.d.ts.map +1 -0
- package/dist/types/TCNSAxon.d.ts +3 -0
- package/dist/types/TCNSAxon.d.ts.map +1 -0
- package/dist/types/TCNSDendrite.d.ts +9 -0
- package/dist/types/TCNSDendrite.d.ts.map +1 -0
- package/dist/types/TCNSLocalContextValueStore.d.ts +5 -0
- package/dist/types/TCNSLocalContextValueStore.d.ts.map +1 -0
- package/dist/types/TCNSNeuron.d.ts +8 -0
- package/dist/types/TCNSNeuron.d.ts.map +1 -0
- package/dist/types/TCNSNeuronResponseReturn.d.ts +3 -0
- package/dist/types/TCNSNeuronResponseReturn.d.ts.map +1 -0
- package/dist/types/TCNSOptions.d.ts +11 -0
- package/dist/types/TCNSOptions.d.ts.map +1 -0
- package/dist/types/TCNSSignal.d.ts +6 -0
- package/dist/types/TCNSSignal.d.ts.map +1 -0
- package/dist/types/TCNSStimulationOptions.d.ts +13 -0
- package/dist/types/TCNSStimulationOptions.d.ts.map +1 -0
- package/dist/types/TCNSStimulationQueueItem.d.ts +10 -0
- package/dist/types/TCNSStimulationQueueItem.d.ts.map +1 -0
- package/dist/types/TCNSStimulationResponse.d.ts +11 -0
- package/dist/types/TCNSStimulationResponse.d.ts.map +1 -0
- package/dist/types/TCNSSubscriber.d.ts +7 -0
- package/dist/types/TCNSSubscriber.d.ts.map +1 -0
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -17,52 +17,337 @@ var CNSStimulationContextStore = class {
|
|
|
17
17
|
this.ctx.set(key, value);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
delete(key) {
|
|
21
|
+
this.ctx.delete(key);
|
|
22
|
+
}
|
|
20
23
|
};
|
|
21
24
|
|
|
22
|
-
// src/
|
|
23
|
-
var
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.
|
|
25
|
+
// src/CNSFunctionalQueue.ts
|
|
26
|
+
var CNSFunctionalQueue = class {
|
|
27
|
+
constructor(processor, concurrency, abortSignal, initialCapacity) {
|
|
28
|
+
this.processor = processor;
|
|
29
|
+
this.concurrency = concurrency;
|
|
30
|
+
this.abortSignal = abortSignal;
|
|
31
|
+
this.items = [];
|
|
28
32
|
this.head = 0;
|
|
33
|
+
this.tail = 0;
|
|
34
|
+
this.size = 0;
|
|
35
|
+
this.capacity = 16;
|
|
36
|
+
this.activeOperations = 0;
|
|
37
|
+
// NEW: prevent re-entrant pump; defer extra runs
|
|
38
|
+
this.pumping = false;
|
|
39
|
+
this.needsPump = false;
|
|
40
|
+
if (initialCapacity && initialCapacity > 0) {
|
|
41
|
+
this.capacity = initialCapacity;
|
|
42
|
+
}
|
|
43
|
+
this.items = new Array(this.capacity);
|
|
29
44
|
}
|
|
30
|
-
|
|
31
|
-
this.
|
|
45
|
+
get canStartOperation() {
|
|
46
|
+
const limit = this.concurrency ?? Infinity;
|
|
47
|
+
return this.activeOperations < limit && !this.abortSignal?.aborted;
|
|
48
|
+
}
|
|
49
|
+
resize() {
|
|
50
|
+
const oldCapacity = this.capacity;
|
|
51
|
+
this.capacity = oldCapacity * 2;
|
|
52
|
+
const newItems = new Array(this.capacity);
|
|
53
|
+
let oldIndex = this.head;
|
|
54
|
+
for (let i = 0; i < this.size; i++) {
|
|
55
|
+
newItems[i] = this.items[oldIndex];
|
|
56
|
+
oldIndex = (oldIndex + 1) % oldCapacity;
|
|
57
|
+
}
|
|
58
|
+
this.items = newItems;
|
|
59
|
+
this.head = 0;
|
|
60
|
+
this.tail = this.size;
|
|
61
|
+
}
|
|
62
|
+
dequeue() {
|
|
63
|
+
if (this.size === 0) return void 0;
|
|
64
|
+
const item = this.items[this.head];
|
|
65
|
+
this.items[this.head] = void 0;
|
|
66
|
+
this.head = (this.head + 1) % this.capacity;
|
|
67
|
+
this.size--;
|
|
68
|
+
return item;
|
|
69
|
+
}
|
|
70
|
+
enqueueItem(item) {
|
|
71
|
+
if (this.size === this.capacity) {
|
|
72
|
+
this.resize();
|
|
73
|
+
}
|
|
74
|
+
this.items[this.tail] = item;
|
|
75
|
+
this.tail = (this.tail + 1) % this.capacity;
|
|
76
|
+
this.size++;
|
|
32
77
|
}
|
|
33
|
-
|
|
34
|
-
if (this.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
78
|
+
pump() {
|
|
79
|
+
if (this.pumping) {
|
|
80
|
+
this.needsPump = true;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.pumping = true;
|
|
84
|
+
while (this.canStartOperation && this.size > 0) {
|
|
85
|
+
const item = this.dequeue();
|
|
86
|
+
this.activeOperations++;
|
|
87
|
+
const ret = this.processor(item);
|
|
88
|
+
if (ret && typeof ret.then === "function") {
|
|
89
|
+
ret.then(
|
|
90
|
+
(cb) => {
|
|
91
|
+
this.activeOperations--;
|
|
92
|
+
if (typeof cb === "function") cb();
|
|
93
|
+
if (this.size > 0 && this.canStartOperation) {
|
|
94
|
+
if (this.pumping) this.needsPump = true;
|
|
95
|
+
else this.pump();
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
(err) => {
|
|
99
|
+
this.activeOperations--;
|
|
100
|
+
if (this.size > 0 && this.canStartOperation) {
|
|
101
|
+
if (this.pumping) this.needsPump = true;
|
|
102
|
+
else this.pump();
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
this.activeOperations--;
|
|
110
|
+
if (typeof ret === "function") ret();
|
|
111
|
+
}
|
|
112
|
+
this.pumping = false;
|
|
113
|
+
if (this.needsPump) {
|
|
114
|
+
this.needsPump = false;
|
|
115
|
+
this.pump();
|
|
39
116
|
}
|
|
40
|
-
|
|
117
|
+
}
|
|
118
|
+
enqueue(x) {
|
|
119
|
+
this.enqueueItem(x);
|
|
120
|
+
if (!this.pumping) this.pump();
|
|
121
|
+
else this.needsPump = true;
|
|
41
122
|
}
|
|
42
123
|
get length() {
|
|
43
|
-
return this.
|
|
124
|
+
return this.size;
|
|
125
|
+
}
|
|
126
|
+
getActiveOperationsCount() {
|
|
127
|
+
return this.activeOperations;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/CNSStimulation.ts
|
|
132
|
+
var CNSStimulation = class {
|
|
133
|
+
constructor(cns, options) {
|
|
134
|
+
this.cns = cns;
|
|
135
|
+
this.options = options;
|
|
136
|
+
this.scheduledCount = 0;
|
|
137
|
+
/**
|
|
138
|
+
* Track active SCCs: SCC index -> count of active neurons in that SCC
|
|
139
|
+
*/
|
|
140
|
+
this.activeSccCounts = /* @__PURE__ */ new Map();
|
|
141
|
+
this.ctx = options?.ctx ?? options?.createContextStore?.() ?? new CNSStimulationContextStore();
|
|
142
|
+
this.id = options?.stimulationId || Math.random().toString(36).slice(2);
|
|
143
|
+
this.queue = new CNSFunctionalQueue(
|
|
144
|
+
(item) => this.processQueueItem(item),
|
|
145
|
+
options?.concurrency,
|
|
146
|
+
options?.abortSignal
|
|
147
|
+
);
|
|
148
|
+
if (this.options?.maxNeuronHops) {
|
|
149
|
+
this.nueronVisitMap = /* @__PURE__ */ new Map();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
get concurrencyEnabled() {
|
|
153
|
+
return this.options?.concurrency !== void 0 && this.options?.concurrency > 0;
|
|
154
|
+
}
|
|
155
|
+
get autoCleanupContextsEnabled() {
|
|
156
|
+
return this.cns?.options?.autoCleanupContexts ?? false;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Mark a neuron as active (being processed)
|
|
160
|
+
*/
|
|
161
|
+
markNeuronActive(neuronId) {
|
|
162
|
+
if (!this.autoCleanupContextsEnabled) return;
|
|
163
|
+
this.incrementSccCount(neuronId);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Mark a neuron as inactive (finished processing)
|
|
167
|
+
*/
|
|
168
|
+
markNeuronInactive(neuronId) {
|
|
169
|
+
if (!this.autoCleanupContextsEnabled) return;
|
|
170
|
+
this.decrementSccCount(neuronId);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Increment the active count for the SCC containing this neuron
|
|
174
|
+
*/
|
|
175
|
+
incrementSccCount(neuronId) {
|
|
176
|
+
const sccIndex = this.cns.getSccIndexByNeuronId(neuronId);
|
|
177
|
+
if (sccIndex === void 0) return;
|
|
178
|
+
const currentCount = this.activeSccCounts.get(sccIndex) || 0;
|
|
179
|
+
this.activeSccCounts.set(sccIndex, currentCount + 1);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Decrement the active count for the SCC containing this neuron
|
|
183
|
+
*/
|
|
184
|
+
decrementSccCount(neuronId) {
|
|
185
|
+
const sccIndex = this.cns.getSccIndexByNeuronId(neuronId);
|
|
186
|
+
if (sccIndex === void 0) return;
|
|
187
|
+
const currentCount = this.activeSccCounts.get(sccIndex) || 0;
|
|
188
|
+
const nextCount = Math.max(0, currentCount - 1);
|
|
189
|
+
this.activeSccCounts.set(sccIndex, nextCount);
|
|
190
|
+
if (currentCount === 0) {
|
|
191
|
+
console.warn(
|
|
192
|
+
`[CNSStimulation] Attempting to decrement SCC count below 0 for neuron ${neuronId}`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check if a neuron can be guaranteed not to be visited again
|
|
198
|
+
*/
|
|
199
|
+
canNeuronBeGuaranteedDone(neuronId) {
|
|
200
|
+
if (!this.autoCleanupContextsEnabled) return false;
|
|
201
|
+
return this.cns.canNeuronBeGuaranteedDone(
|
|
202
|
+
neuronId,
|
|
203
|
+
this.activeSccCounts
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
cleanupCtxIfNeeded(neuron2) {
|
|
207
|
+
if (this.autoCleanupContextsEnabled && this.canNeuronBeGuaranteedDone(neuron2.id)) {
|
|
208
|
+
this.ctx.delete(neuron2.id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
createSubscriberQueueItem(subscriber, inputSignal) {
|
|
212
|
+
if (this.options?.maxNeuronHops) {
|
|
213
|
+
if (this.nueronVisitMap?.get(subscriber.neuron.id) ?? 0 >= this.options?.maxNeuronHops) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Max neuron hops reached for neuron "${subscriber.neuron.id}" when trying to enqueue subscriber "${subscriber.dendrite.collateral.id}" in stimulation "${this.id}"`
|
|
216
|
+
);
|
|
217
|
+
} else {
|
|
218
|
+
this.nueronVisitMap?.set(
|
|
219
|
+
subscriber.neuron.id,
|
|
220
|
+
(this.nueronVisitMap?.get(subscriber.neuron.id) ?? 0) + 1
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
neuronId: subscriber.neuron.id,
|
|
226
|
+
subscriber,
|
|
227
|
+
inputSignal
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
processQueueItem(item) {
|
|
231
|
+
const { subscriber, inputSignal } = item;
|
|
232
|
+
const { neuron: neuron2, dendrite } = subscriber;
|
|
233
|
+
this.markNeuronActive(neuron2.id);
|
|
234
|
+
const response = dendrite.response(
|
|
235
|
+
inputSignal?.payload,
|
|
236
|
+
neuron2.axon,
|
|
237
|
+
{
|
|
238
|
+
get: () => this.ctx.get(neuron2.id),
|
|
239
|
+
set: (value) => this.ctx.set(neuron2.id, value)
|
|
240
|
+
},
|
|
241
|
+
this.options?.abortSignal
|
|
242
|
+
);
|
|
243
|
+
if (response instanceof Promise) {
|
|
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
|
+
};
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
} else {
|
|
266
|
+
return () => {
|
|
267
|
+
this.markNeuronInactive(neuron2.id);
|
|
268
|
+
return this.processResponse(
|
|
269
|
+
inputSignal,
|
|
270
|
+
response
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
processResponse(inputSignal, outputSignal, error) {
|
|
276
|
+
const collateral2 = outputSignal?.collateral;
|
|
277
|
+
const ownerNeuron = collateral2 ? this.cns.getParentNeuronByCollateralId(collateral2.id) : void 0;
|
|
278
|
+
const subscribers = collateral2 ? this.cns.getSubscribers(collateral2.id) : [];
|
|
279
|
+
const subscriberQueueItems = [];
|
|
280
|
+
if (collateral2 && !error) {
|
|
281
|
+
if (ownerNeuron) {
|
|
282
|
+
this.cleanupCtxIfNeeded(ownerNeuron);
|
|
283
|
+
}
|
|
284
|
+
for (let i = 0; i < subscribers.length; i++) {
|
|
285
|
+
subscriberQueueItems.push(
|
|
286
|
+
this.createSubscriberQueueItem(subscribers[i], outputSignal)
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
this.scheduledCount += subscriberQueueItems.length;
|
|
291
|
+
this.options?.onResponse?.({
|
|
292
|
+
inputSignal,
|
|
293
|
+
outputSignal,
|
|
294
|
+
ctx: this.ctx,
|
|
295
|
+
queueLength: this.queue.length + this.scheduledCount,
|
|
296
|
+
hops: this.options?.maxNeuronHops && ownerNeuron ? this.nueronVisitMap?.get(ownerNeuron.id) ?? 0 : void 0,
|
|
297
|
+
error
|
|
298
|
+
});
|
|
299
|
+
for (let i = 0; i < subscriberQueueItems.length; i++) {
|
|
300
|
+
this.scheduledCount--;
|
|
301
|
+
this.queue.enqueue(subscriberQueueItems[i]);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
responseToSignal(signal) {
|
|
305
|
+
const ownerNeuron = this.cns.getParentNeuronByCollateralId(
|
|
306
|
+
signal.collateral.id
|
|
307
|
+
);
|
|
308
|
+
if (ownerNeuron) {
|
|
309
|
+
this.markNeuronActive(ownerNeuron.id);
|
|
310
|
+
}
|
|
311
|
+
this.processResponse(void 0, signal);
|
|
44
312
|
}
|
|
45
|
-
// Optional convenience if you need to inspect without removing:
|
|
46
|
-
// peek(): T | undefined { return this.a[this.head]; }
|
|
47
313
|
};
|
|
314
|
+
|
|
315
|
+
// src/CNS.ts
|
|
48
316
|
var CNS = class {
|
|
49
|
-
constructor(neurons) {
|
|
317
|
+
constructor(neurons, options) {
|
|
50
318
|
this.neurons = neurons;
|
|
319
|
+
this.options = options;
|
|
51
320
|
/**
|
|
52
321
|
* Fast lookup: collateralId -> list of (neuron, dendrite) subscribers.
|
|
53
322
|
* Built once at construction time.
|
|
54
323
|
*/
|
|
55
324
|
this.subIndex = /* @__PURE__ */ new Map();
|
|
325
|
+
this.parentNeuronByCollateralId = /* @__PURE__ */ new Map();
|
|
56
326
|
/**
|
|
57
|
-
*
|
|
327
|
+
* Strongly Connected Components of the neuron graph.
|
|
328
|
+
* Each component is a set of neuron IDs that can reach each other.
|
|
58
329
|
*/
|
|
59
|
-
this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
330
|
+
this.stronglyConnectedComponents = [];
|
|
331
|
+
/**
|
|
332
|
+
* Quick lookup: neuron ID -> SCC index for fast reachability checks.
|
|
333
|
+
*/
|
|
334
|
+
this.neuronToSCC = /* @__PURE__ */ new Map();
|
|
335
|
+
/**
|
|
336
|
+
* DAG of SCCs: SCC index -> set of SCC indices that can reach this SCC
|
|
337
|
+
*/
|
|
338
|
+
this.sccDag = /* @__PURE__ */ new Map();
|
|
339
|
+
/**
|
|
340
|
+
* Precomputed ancestor sets for each SCC for fast reachability checks
|
|
341
|
+
*/
|
|
342
|
+
this.sccAncestors = /* @__PURE__ */ new Map();
|
|
343
|
+
this.buildIndexes();
|
|
344
|
+
if (this.options?.autoCleanupContexts) {
|
|
345
|
+
this.buildSCC();
|
|
346
|
+
}
|
|
63
347
|
}
|
|
64
|
-
|
|
348
|
+
buildIndexes() {
|
|
65
349
|
this.subIndex.clear();
|
|
350
|
+
this.parentNeuronByCollateralId.clear();
|
|
66
351
|
for (const neuron2 of this.neurons) {
|
|
67
352
|
for (const dendrite of neuron2.dendrites) {
|
|
68
353
|
const key = dendrite.collateral.id;
|
|
@@ -73,218 +358,210 @@ var CNS = class {
|
|
|
73
358
|
arr
|
|
74
359
|
);
|
|
75
360
|
}
|
|
361
|
+
Object.values(neuron2.axon).forEach((collateral2) => {
|
|
362
|
+
this.parentNeuronByCollateralId.set(
|
|
363
|
+
collateral2.id,
|
|
364
|
+
neuron2
|
|
365
|
+
);
|
|
366
|
+
});
|
|
76
367
|
}
|
|
77
368
|
}
|
|
78
369
|
/**
|
|
79
|
-
*
|
|
80
|
-
* dedup key generation during stimulation.
|
|
370
|
+
* Build the neuron graph where neurons are connected if one can stimulate the other.
|
|
81
371
|
*/
|
|
82
|
-
|
|
372
|
+
buildNeuronGraph() {
|
|
373
|
+
const graph = /* @__PURE__ */ new Map();
|
|
374
|
+
const neuronIds = this.neurons.map((n) => n.id);
|
|
375
|
+
for (const neuronId of neuronIds) {
|
|
376
|
+
graph.set(neuronId, /* @__PURE__ */ new Set());
|
|
377
|
+
}
|
|
83
378
|
for (const neuron2 of this.neurons) {
|
|
84
|
-
|
|
85
|
-
for (const
|
|
86
|
-
|
|
379
|
+
const reachableNeurons = /* @__PURE__ */ new Set();
|
|
380
|
+
for (const axonKey in neuron2.axon) {
|
|
381
|
+
const collateral2 = neuron2.axon[axonKey];
|
|
382
|
+
const collateralId = collateral2.id;
|
|
383
|
+
const subscribers = this.subIndex.get(collateralId);
|
|
384
|
+
if (subscribers) {
|
|
385
|
+
for (const { neuron: targetNeuron } of subscribers) {
|
|
386
|
+
reachableNeurons.add(targetNeuron.id);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
87
389
|
}
|
|
390
|
+
graph.set(neuron2.id, reachableNeurons);
|
|
88
391
|
}
|
|
392
|
+
return graph;
|
|
89
393
|
}
|
|
90
394
|
/**
|
|
91
|
-
*
|
|
395
|
+
* Build strongly connected components using Tarjan's algorithm.
|
|
92
396
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
397
|
+
buildSCC() {
|
|
398
|
+
const graph = this.buildNeuronGraph();
|
|
399
|
+
const neuronIds = this.neurons.map((n) => n.id);
|
|
400
|
+
const index = /* @__PURE__ */ new Map();
|
|
401
|
+
const lowlink = /* @__PURE__ */ new Map();
|
|
402
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
403
|
+
const stack = [];
|
|
404
|
+
const components = [];
|
|
405
|
+
let currentIndex = 0;
|
|
406
|
+
const strongConnect = (neuronId) => {
|
|
407
|
+
index.set(neuronId, currentIndex);
|
|
408
|
+
lowlink.set(neuronId, currentIndex);
|
|
409
|
+
currentIndex++;
|
|
410
|
+
stack.push(neuronId);
|
|
411
|
+
onStack.add(neuronId);
|
|
412
|
+
const neighbors = graph.get(neuronId) || /* @__PURE__ */ new Set();
|
|
413
|
+
for (const neighbor of neighbors) {
|
|
414
|
+
if (!index.has(neighbor)) {
|
|
415
|
+
strongConnect(neighbor);
|
|
416
|
+
lowlink.set(
|
|
417
|
+
neuronId,
|
|
418
|
+
Math.min(lowlink.get(neuronId), lowlink.get(neighbor))
|
|
419
|
+
);
|
|
420
|
+
} else if (onStack.has(neighbor)) {
|
|
421
|
+
lowlink.set(
|
|
422
|
+
neuronId,
|
|
423
|
+
Math.min(lowlink.get(neuronId), index.get(neighbor))
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (lowlink.get(neuronId) === index.get(neuronId)) {
|
|
428
|
+
const component = /* @__PURE__ */ new Set();
|
|
429
|
+
let w;
|
|
430
|
+
do {
|
|
431
|
+
w = stack.pop();
|
|
432
|
+
onStack.delete(w);
|
|
433
|
+
component.add(w);
|
|
434
|
+
} while (w !== neuronId);
|
|
435
|
+
components.push(component);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
for (const neuronId of neuronIds) {
|
|
439
|
+
if (!index.has(neuronId)) {
|
|
440
|
+
strongConnect(neuronId);
|
|
441
|
+
}
|
|
99
442
|
}
|
|
100
|
-
|
|
443
|
+
this.stronglyConnectedComponents = components;
|
|
444
|
+
for (let i = 0; i < components.length; i++) {
|
|
445
|
+
for (const neuronId of components[i]) {
|
|
446
|
+
this.neuronToSCC.set(neuronId, i);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
this.buildSCCDAG(graph);
|
|
450
|
+
this.buildSCCAncestors();
|
|
101
451
|
}
|
|
102
452
|
/**
|
|
103
|
-
*
|
|
453
|
+
* Build DAG between SCCs based on the original neuron graph
|
|
104
454
|
*/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
455
|
+
buildSCCDAG(neuronGraph) {
|
|
456
|
+
this.sccDag.clear();
|
|
457
|
+
for (let i = 0; i < this.stronglyConnectedComponents.length; i++) {
|
|
458
|
+
this.sccDag.set(i, /* @__PURE__ */ new Set());
|
|
459
|
+
}
|
|
460
|
+
for (let i = 0; i < this.stronglyConnectedComponents.length; i++) {
|
|
461
|
+
const scc = this.stronglyConnectedComponents[i];
|
|
462
|
+
for (const neuronId of scc) {
|
|
463
|
+
const neighbors = neuronGraph.get(neuronId) || /* @__PURE__ */ new Set();
|
|
464
|
+
for (const neighborId of neighbors) {
|
|
465
|
+
const neighborSccIndex = this.neuronToSCC.get(neighborId);
|
|
466
|
+
if (neighborSccIndex !== void 0 && neighborSccIndex !== i) {
|
|
467
|
+
this.sccDag.get(neighborSccIndex).add(i);
|
|
468
|
+
}
|
|
113
469
|
}
|
|
114
470
|
}
|
|
115
|
-
cache.set(str, hash);
|
|
116
|
-
} else {
|
|
117
|
-
cache.delete(str);
|
|
118
|
-
cache.set(str, hash);
|
|
119
471
|
}
|
|
120
|
-
return hash;
|
|
121
472
|
}
|
|
122
473
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* Includes hops to allow signals to pass through the same neuron at different levels.
|
|
474
|
+
* Precompute ancestor sets for each SCC for fast reachability checks
|
|
475
|
+
* Uses topological sort for efficient computation
|
|
126
476
|
*/
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
* - Asynchronous responses:
|
|
140
|
-
* • We DO NOT await them (no per-item barrier).
|
|
141
|
-
* • We attach `.then` to enqueue outgoing signals when ready.
|
|
142
|
-
* • We notify the scheduler when the first async for this item starts,
|
|
143
|
-
* and when the last async for this item settles.
|
|
144
|
-
* - No error handling: we do not try/catch. Rejections bubble via wireRejectionToRun().
|
|
145
|
-
*
|
|
146
|
-
* Returns:
|
|
147
|
-
* boolean — whether this item launched any async work (>= 1 promises).
|
|
148
|
-
*/
|
|
149
|
-
fanOutFireAndForget(incoming, allowType, seen, queue, ctx, enableDedup, onItemFirstAsyncStart, onItemAllAsyncSettled, onAnyAsyncSettled, onTrace) {
|
|
150
|
-
const subscribers = this.subIndex.get(incoming.collateralId);
|
|
151
|
-
if (!subscribers || subscribers.length === 0) return false;
|
|
152
|
-
const nextHop = incoming.hops + 1;
|
|
153
|
-
const spikeId = incoming.spikeId;
|
|
154
|
-
const enqueueOut = (out) => {
|
|
155
|
-
if (out == null) return;
|
|
156
|
-
for (const spike of asArray(out)) {
|
|
157
|
-
const t = spike.type;
|
|
158
|
-
if (allowType && !allowType(t)) continue;
|
|
159
|
-
queue.push({
|
|
160
|
-
collateralId: t,
|
|
161
|
-
payload: spike.payload,
|
|
162
|
-
hops: nextHop,
|
|
163
|
-
spikeId
|
|
164
|
-
});
|
|
477
|
+
buildSCCAncestors() {
|
|
478
|
+
this.sccAncestors.clear();
|
|
479
|
+
for (let i = 0; i < this.stronglyConnectedComponents.length; i++) {
|
|
480
|
+
this.sccAncestors.set(i, /* @__PURE__ */ new Set());
|
|
481
|
+
}
|
|
482
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
483
|
+
const queue = [];
|
|
484
|
+
for (let i = 0; i < this.stronglyConnectedComponents.length; i++) {
|
|
485
|
+
const incomingEdges = this.sccDag.get(i) || /* @__PURE__ */ new Set();
|
|
486
|
+
inDegree.set(i, incomingEdges.size);
|
|
487
|
+
if (incomingEdges.size === 0) {
|
|
488
|
+
queue.push(i);
|
|
165
489
|
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
});
|
|
182
|
-
if (isPromise(res)) {
|
|
183
|
-
if (!itemMarkedAsync) {
|
|
184
|
-
itemMarkedAsync = true;
|
|
185
|
-
onItemFirstAsyncStart();
|
|
490
|
+
}
|
|
491
|
+
while (queue.length > 0) {
|
|
492
|
+
const current = queue.shift();
|
|
493
|
+
const outgoingEdges = this.getOutgoingEdges(current);
|
|
494
|
+
for (const neighbor of outgoingEdges) {
|
|
495
|
+
const neighborAncestors = this.sccAncestors.get(neighbor);
|
|
496
|
+
neighborAncestors.add(current);
|
|
497
|
+
const currentAncestors = this.sccAncestors.get(current);
|
|
498
|
+
for (const ancestor of currentAncestors) {
|
|
499
|
+
neighborAncestors.add(ancestor);
|
|
500
|
+
}
|
|
501
|
+
const newInDegree = (inDegree.get(neighbor) || 0) - 1;
|
|
502
|
+
inDegree.set(neighbor, newInDegree);
|
|
503
|
+
if (newInDegree === 0) {
|
|
504
|
+
queue.push(neighbor);
|
|
186
505
|
}
|
|
187
|
-
openAsync++;
|
|
188
|
-
res.then(
|
|
189
|
-
(out) => {
|
|
190
|
-
enqueueOut(out);
|
|
191
|
-
openAsync--;
|
|
192
|
-
onAnyAsyncSettled();
|
|
193
|
-
if (openAsync === 0) {
|
|
194
|
-
onItemAllAsyncSettled();
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
(err) => {
|
|
198
|
-
onTrace?.({
|
|
199
|
-
collateralId: incoming.collateralId,
|
|
200
|
-
hops: incoming.hops,
|
|
201
|
-
payload: incoming.payload,
|
|
202
|
-
queueLength: queue.length,
|
|
203
|
-
error: err,
|
|
204
|
-
contextStore: ctx
|
|
205
|
-
// Pass the context store interface
|
|
206
|
-
});
|
|
207
|
-
openAsync--;
|
|
208
|
-
onAnyAsyncSettled();
|
|
209
|
-
if (openAsync === 0) {
|
|
210
|
-
onItemAllAsyncSettled();
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
);
|
|
214
|
-
} else {
|
|
215
|
-
enqueueOut(res);
|
|
216
506
|
}
|
|
217
507
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
collateralId: collateral2.id,
|
|
228
|
-
payload,
|
|
229
|
-
hops: 0,
|
|
230
|
-
spikeId
|
|
231
|
-
});
|
|
232
|
-
const seen = /* @__PURE__ */ new Set();
|
|
233
|
-
let activeOperations = 0;
|
|
234
|
-
const startOperation = () => {
|
|
235
|
-
activeOperations++;
|
|
236
|
-
};
|
|
237
|
-
const finishOperation = () => {
|
|
238
|
-
activeOperations--;
|
|
239
|
-
tryProcessMore();
|
|
240
|
-
};
|
|
241
|
-
const canStartOperation = () => {
|
|
242
|
-
return activeOperations < limit && !opts?.abortSignal?.aborted;
|
|
243
|
-
};
|
|
244
|
-
const processItem = (item) => {
|
|
245
|
-
opts?.onTrace?.({
|
|
246
|
-
collateralId: item.collateralId,
|
|
247
|
-
hops: item.hops,
|
|
248
|
-
payload: item.payload,
|
|
249
|
-
queueLength: queue.length,
|
|
250
|
-
contextStore: ctx
|
|
251
|
-
// Pass the context store
|
|
252
|
-
});
|
|
253
|
-
if (maxHops && item.hops >= maxHops) {
|
|
254
|
-
finishOperation();
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
const onOperationComplete = () => {
|
|
258
|
-
finishOperation();
|
|
259
|
-
};
|
|
260
|
-
const onAsyncStart = () => {
|
|
261
|
-
};
|
|
262
|
-
const launchedAsync = this.fanOutFireAndForget(
|
|
263
|
-
item,
|
|
264
|
-
opts?.allowType,
|
|
265
|
-
seen,
|
|
266
|
-
queue,
|
|
267
|
-
ctx,
|
|
268
|
-
maxHops !== void 0,
|
|
269
|
-
// Enable dedup only if maxHops is set
|
|
270
|
-
onAsyncStart,
|
|
271
|
-
onOperationComplete,
|
|
272
|
-
tryProcessMore,
|
|
273
|
-
// Continue streaming on any completion
|
|
274
|
-
opts?.onTrace
|
|
275
|
-
);
|
|
276
|
-
if (!launchedAsync) {
|
|
277
|
-
finishOperation();
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Get outgoing edges for a given SCC index
|
|
511
|
+
*/
|
|
512
|
+
getOutgoingEdges(sccIndex) {
|
|
513
|
+
const outgoing = /* @__PURE__ */ new Set();
|
|
514
|
+
for (const [targetScc, incomingEdges] of this.sccDag) {
|
|
515
|
+
if (incomingEdges.has(sccIndex)) {
|
|
516
|
+
outgoing.add(targetScc);
|
|
278
517
|
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
518
|
+
}
|
|
519
|
+
return outgoing;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Check if a neuron can be reached again (including self-calling).
|
|
523
|
+
* Returns true if the neuron is in a strongly connected component with more than one member,
|
|
524
|
+
* or if it's in a single-member SCC that can reach itself.
|
|
525
|
+
*/
|
|
526
|
+
getSCCSetByNeuronId(neuronId) {
|
|
527
|
+
const sccIndex = this.neuronToSCC.get(neuronId);
|
|
528
|
+
if (sccIndex === void 0) return;
|
|
529
|
+
return this.stronglyConnectedComponents[sccIndex];
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Get the SCC index for a given neuron ID
|
|
533
|
+
*/
|
|
534
|
+
getSccIndexByNeuronId(neuronId) {
|
|
535
|
+
return this.neuronToSCC.get(neuronId);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Check if a neuron can be guaranteed not to be visited again during the current propagation.
|
|
539
|
+
* This is the core logic for safe context cleanup.
|
|
540
|
+
*/
|
|
541
|
+
canNeuronBeGuaranteedDone(neuronId, activeSccCounts) {
|
|
542
|
+
const sccIndex = this.neuronToSCC.get(neuronId);
|
|
543
|
+
if (sccIndex === void 0) return true;
|
|
544
|
+
if (activeSccCounts.get(sccIndex) && activeSccCounts.get(sccIndex) > 0) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
const ancestors = this.sccAncestors.get(sccIndex);
|
|
548
|
+
if (!ancestors) return true;
|
|
549
|
+
for (const ancestorSccIndex of ancestors) {
|
|
550
|
+
if (activeSccCounts.get(ancestorSccIndex) && activeSccCounts.get(ancestorSccIndex) > 0) {
|
|
551
|
+
return false;
|
|
285
552
|
}
|
|
286
|
-
}
|
|
287
|
-
|
|
553
|
+
}
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
getParentNeuronByCollateralId(collateralId) {
|
|
557
|
+
return this.parentNeuronByCollateralId.get(collateralId);
|
|
558
|
+
}
|
|
559
|
+
getSubscribers(collateralId) {
|
|
560
|
+
return this.subIndex.get(collateralId) ?? [];
|
|
561
|
+
}
|
|
562
|
+
stimulate(signal, options) {
|
|
563
|
+
const stimulation = new CNSStimulation(this, options);
|
|
564
|
+
stimulation.responseToSignal(signal);
|
|
288
565
|
}
|
|
289
566
|
};
|
|
290
567
|
|
|
@@ -293,9 +570,8 @@ var CNSCollateral = class {
|
|
|
293
570
|
constructor(id) {
|
|
294
571
|
this.id = id;
|
|
295
572
|
}
|
|
296
|
-
createSignal(
|
|
297
|
-
|
|
298
|
-
return { type: this.id, payload };
|
|
573
|
+
createSignal(payload) {
|
|
574
|
+
return { collateral: this, payload };
|
|
299
575
|
}
|
|
300
576
|
};
|
|
301
577
|
|