KNI spike: engine core + Friflo render via WebGL in the browser
spikes/KniWeb (outside LittleSim.sln): a kni-blazor-gl template project (KNI 4.2.9001, net8.0) referencing MrGameEng.Core directly. A mini-host in the GameHost mold drives EngineContext/GameClock/Scene phases over KNI's Game; the scene moves 300 Friflo entities in the update phase and draws them with SpriteBatch (WebGL). Verified in a real browser: sprites render and animate, browser console is clean. Decision (docs/web-client.md): path A — KNI — is the primary route for the web client; the core runs in Blazor WASM unchanged thanks to the Core/Host split. Known follow-ups: per-platform compilation of the graphics libraries against nkast.* packages, shader compatibility for Renderer2D, HTTP-served content instead of the filesystem. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
8a7e2cce52
commit
ce8f8ba2ed
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,77 @@
|
||||
// micProcessor.js
|
||||
class MicProcessor extends AudioWorkletProcessor
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
// global variables for testing
|
||||
var sampleRate = globalThis.sampleRate;
|
||||
var currentFrame = globalThis.currentFrame;
|
||||
var currentTime = globalThis.currentTime;
|
||||
var currentRenderQuantum = globalThis.currentRenderQuantum;
|
||||
|
||||
this.SampleRate = sampleRate;
|
||||
this.TargetSamples = Math.floor(this.SampleRate * 0.1); // 100ms
|
||||
this.Buffer = new Float32Array(this.TargetSamples);
|
||||
this.BufferIndex = 0;
|
||||
|
||||
this.port.onmessage = (event) =>
|
||||
{
|
||||
var data = event.data;
|
||||
|
||||
if (typeof data === 'number')
|
||||
{
|
||||
//this.port.postMessage(data); // echo back test
|
||||
}
|
||||
if (data instanceof Uint8Array)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
process(inputs, outputs, parameters)
|
||||
{
|
||||
var inChannel0 = inputs[0][0];
|
||||
if (!inChannel0) return true;
|
||||
|
||||
let srcIndex = 0;
|
||||
var srcLen = inChannel0.length;
|
||||
|
||||
while (srcIndex < srcLen)
|
||||
{
|
||||
var remaining = this.TargetSamples - this.BufferIndex;
|
||||
var copyCount = Math.min(remaining, srcLen - srcIndex);
|
||||
|
||||
this.Buffer.set(
|
||||
inChannel0.subarray(srcIndex, srcIndex + copyCount),
|
||||
this.BufferIndex);
|
||||
|
||||
this.BufferIndex += copyCount;
|
||||
srcIndex += copyCount;
|
||||
|
||||
if (this.BufferIndex >= this.TargetSamples)
|
||||
{
|
||||
this.SendBuffer();
|
||||
this.BufferIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SendBuffer()
|
||||
{
|
||||
// convert to 16-6bit PCM
|
||||
var int16 = new Int16Array(this.TargetSamples);
|
||||
for (var i = 0; i < this.TargetSamples; i++)
|
||||
{
|
||||
int16[i] = this.Buffer[i] * 32767;
|
||||
}
|
||||
|
||||
var byteArray = new Uint8Array(int16.buffer);
|
||||
this.port.postMessage(byteArray, [byteArray.buffer]);
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('mic-processor', MicProcessor);
|
||||
@@ -0,0 +1,87 @@
|
||||
// streamProcessor.js
|
||||
class StreamProcessor extends AudioWorkletProcessor
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
this.queue = [];
|
||||
|
||||
this.port.onmessage = (event) =>
|
||||
{
|
||||
var data = event.data;
|
||||
|
||||
if (typeof data === 'number')
|
||||
{
|
||||
if (data === 2)
|
||||
{
|
||||
this.queue = [];
|
||||
}
|
||||
}
|
||||
if (data instanceof Uint8Array)
|
||||
{
|
||||
const buffer = new Int16Array(data.buffer, data.byteOffset, data.length / 2);
|
||||
buffer.offset = 0;
|
||||
this.queue.push(buffer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
process(inputs, outputs, parameters)
|
||||
{
|
||||
const output = outputs[0];
|
||||
|
||||
const channelCount = output.length;
|
||||
const sampleCount = output[0].length;
|
||||
|
||||
let written = 0;
|
||||
|
||||
while (written < sampleCount && this.queue.length > 0)
|
||||
{
|
||||
const buffer = this.queue[0];
|
||||
const offset = buffer.offset;
|
||||
|
||||
const available = buffer.length - offset;
|
||||
const needed = sampleCount - written;
|
||||
const copyCount = Math.min(available, needed);
|
||||
|
||||
for (let i = 0; i < copyCount; i++)
|
||||
{
|
||||
for (let c = 0; c < channelCount; c++)
|
||||
{
|
||||
const channel = output[c];
|
||||
let value = (buffer[offset+i] / 32767);
|
||||
channel[written+i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
written += copyCount;
|
||||
buffer.offset += copyCount;
|
||||
|
||||
if (buffer.offset >= buffer.length)
|
||||
{
|
||||
this.queue.shift();
|
||||
this.port.postMessage(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Fill remaining samples with silence
|
||||
if (written < sampleCount)
|
||||
{
|
||||
for (let c = 0; c < channelCount; c++)
|
||||
{
|
||||
const channel = output[c];
|
||||
|
||||
for (let i = written; i < sampleCount; i++)
|
||||
{
|
||||
let value = 0;
|
||||
channel[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor("stream-processor", StreamProcessor);
|
||||
Reference in New Issue
Block a user