// Structure-of-arrays storage for parsed G-code segments. // // Segment i runs from positions[i] to positions[i + 1], so the path is a // single shared polyline and every other field is one entry per segment. The // same class is used by the parser worker (filling it) and the main thread // (receiving transferred chunks and feeding GPU buffers). export const SEGMENT_FIELDS = [ ['type', Uint8Array], ['tool', Uint8Array], ['fan', Uint8Array], ['layer', Uint16Array], ['temp', Uint16Array], ['width', Float32Array], ['height', Float32Array], ['feed', Float32Array], ['accel', Float32Array], ['offset', Uint32Array] ] // Filled after the planner pass, not part of streamed chunks. export const PLANNER_FIELDS = [ ['actualSpeed', Float32Array], ['segmentTime', Float32Array], ['layerTime', Float32Array] ] const ALL_FIELDS = [...SEGMENT_FIELDS, ...PLANNER_FIELDS] export default class SegmentStore { constructor(capacity = 1024) { this.count = 0 this.capacity = 0 // Bumped whenever the backing arrays are reallocated so GPU-side // consumers know to rebuild their attribute bindings. this.generation = 0 this.positions = null this.plannerReady = false this.allocate(Math.max(16, capacity | 0)) } allocate(capacity) { const oldCount = this.count const oldPositions = this.positions const nextPositions = new Float32Array((capacity + 1) * 3) if (oldPositions) { nextPositions.set(oldPositions.subarray(0, (oldCount + 1) * 3)) } this.positions = nextPositions for (const [name, Type] of ALL_FIELDS) { const next = new Type(capacity) if (this[name]) next.set(this[name].subarray(0, oldCount)) this[name] = next } this.capacity = capacity this.generation++ } ensureCapacity(required) { if (required <= this.capacity) return let next = this.capacity while (next < required) next = Math.ceil(next * 1.5) + 1024 this.allocate(next) } // Worker side: append one segment. The start point is written here and the // end point is whatever the next push (or setEnd) writes. push( x, y, z, type, tool, fan, layer, temp, width, height, feed, accel, offset ) { const i = this.count if (i + 1 > this.capacity) this.ensureCapacity(i + 1) const p = i * 3 this.positions[p] = x this.positions[p + 1] = y this.positions[p + 2] = z this.type[i] = type this.tool[i] = tool this.fan[i] = fan this.layer[i] = layer > 65535 ? 65535 : layer this.temp[i] = temp < 0 ? 0 : temp > 65535 ? 65535 : temp this.width[i] = width this.height[i] = height this.feed[i] = feed this.accel[i] = accel this.offset[i] = offset this.count = i + 1 return i } setEnd(x, y, z) { const p = this.count * 3 this.positions[p] = x this.positions[p + 1] = y this.positions[p + 2] = z } // Worker side: copy segments [from, to) into transferable buffers. sliceChunk(from, to) { const count = to - from const chunk = { start: from, count } const transfer = [] // Start points for every segment in the range plus the end of the last // one, which is just the next start (or the final end point). const positions = this.positions.slice(from * 3, (to + 1) * 3) chunk.positions = positions transfer.push(positions.buffer) for (const [name] of SEGMENT_FIELDS) { const copy = this[name].slice(from, to) chunk[name] = copy transfer.push(copy.buffer) } return { chunk, transfer } } // Main thread side: append a chunk produced by sliceChunk. appendChunk(chunk) { const { start, count } = chunk if (start !== this.count) { throw new Error( `SegmentStore chunk out of order (expected ${this.count}, got ${start})` ) } this.ensureCapacity(start + count) this.positions.set(chunk.positions, start * 3) for (const [name] of SEGMENT_FIELDS) { this[name].set(chunk[name], start) } this.count = start + count } applyPlanner({ actualSpeed, segmentTime, layerTime }) { if (actualSpeed) this.actualSpeed.set(actualSpeed.subarray(0, this.count)) if (segmentTime) this.segmentTime.set(segmentTime.subarray(0, this.count)) if (layerTime) this.layerTime.set(layerTime.subarray(0, this.count)) this.plannerReady = true } getStart(i, out) { const p = i * 3 out[0] = this.positions[p] out[1] = this.positions[p + 1] out[2] = this.positions[p + 2] return out } getEnd(i, out) { return this.getStart(i + 1, out) } // Index of the last segment whose source line starts at or before the given // byte offset, or -1 when the offset precedes every segment. segmentAtByteOffset(byteOffset) { const offsets = this.offset let lo = 0 let hi = this.count - 1 let result = -1 while (lo <= hi) { const mid = (lo + hi) >>> 1 if (offsets[mid] <= byteOffset) { result = mid lo = mid + 1 } else { hi = mid - 1 } } return result } }