File size: 3,367 Bytes
cc38b25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import { loadGraph, configureAssetBase } from './data-loader.js';
import { BrainCPU } from './brain.js';
import { PulseBank, populations, decodeCounts } from './stimulus.js';
let graph,
  brain,
  groups,
  pulses,
  backend = 'cpu',
  generation = 0;
let queue = Promise.resolve();
self.onmessage = ({ data }) => {
  queue = queue
    .then(() => handle(data))
    .catch((error) => postMessage({ type: 'error', message: error.message, generation }));
};
async function handle(m) {
  if (m.type === 'init') {
    configureAssetBase(m.assetBase);
    graph = await loadGraph(
      (value) => postMessage({ type: 'progress', value }),
      (message) => postMessage({ type: 'stage', message }),
    );
    // Shiu's monoamine convention; histamine/unknown remain omitted in this adaptation.
    graph.neurons.forEach((r, i) => {
      if (['dopamine', 'octopamine', 'serotonin'].includes(r[4])) graph.sign[i] = 1;
    });
    groups = populations(graph.neurons);
    pulses = new PulseBank(graph.n);
    if (m.backend !== 'cpu')
      try {
        postMessage({ type: 'stage', message: 'Checking WebGPU against JavaScript…' });
        const { BrainGPU } = await import('./brain-gpu.js');
        const { checkGPU } = await import('./gpu-check.js');
        await checkGPU((g) => BrainGPU.create(g));
        postMessage({ type: 'stage', message: 'Preparing resident connectome and motor readout…' });
        brain = await BrainGPU.create(graph);
        await brain.prepareReadout(groups);
        backend = 'gpu';
      } catch (error) {
        brain?.destroy?.();
        postMessage({ type: 'fallback', message: error.message });
        brain = new BrainCPU(graph);
        backend = 'cpu';
      }
    else brain = new BrainCPU(graph);
    postMessage({ type: 'ready', backend });
  } else if (m.type === 'pulse') {
    if (m.replace) pulses.reset();
    pulses.add(m.indices, brain.tick, m.strength, m.profile ?? 'paint');
  } else if (m.type === 'reset') {
    generation = m.generation;
    await brain.reset();
    pulses.reset();
    postMessage({ type: 'reset', generation });
  } else if (m.type === 'clear') {
    pulses.reset();
  } else if (m.type === 'step') {
    if (m.generation !== generation) return;
    const started = performance.now(),
      steps = 100;
    const result = await brain.batch(steps, pulses.sample(brain.tick), m.silenced);
    const reference = decodeCounts(result.counts, groups, steps);
    let rates = reference;
    if (backend === 'gpu') {
      rates = result.rates;
      for (let c = 0; c < rates.length; c++)
        if (
          !Number.isFinite(rates[c]) ||
          Math.abs(rates[c] - reference[c]) > 1e-3 * Math.max(1, reference[c])
        )
          throw Error('GPU population readout disagrees with JavaScript');
    }
    const ids = [],
      values = [];
    for (let i = 0; i < result.counts.length; i++)
      if (result.counts[i]) {
        ids.push(i);
        values.push(result.counts[i]);
      }
    const firing = Uint32Array.from(ids),
      counts = Uint16Array.from(values);
    postMessage(
      {
        type: 'result',
        generation,
        tick: result.tick,
        steps,
        total: result.total,
        rates,
        firing,
        counts,
        wallMs: performance.now() - started,
      },
      [rates.buffer, firing.buffer, counts.buffer],
    );
  }
}