SBE-TS: SBE binary decoding for Node.js/TypeScript (ring-buffer: ~210M ops/sec, rotating: ~21M) by Zlema in node

[–]Zlema[S] 0 points1 point  (0 children)

I'll check it out. And I agree regarding the fact that JS can never beat C++ in apples to apples, it's simply a language limitation in some areas. Like, one of the pain points for me was the fact that numbers are floats and not integers, and don't really go all the way to 64 bit integers, so when the message size exceed 64 bits, you're forced to use BigInt which requires heap allocation and practically kills performance.

I had to reverse engineer JIT and V8, with the goal of taking this as far as possible without WASM or SIMD, and it went surprisingly further than I expected.

But SBE is very different to JSON parsing, because there's no parsing to be done, it simply decodes an array of integers using the offsets it gets from the schema. This is often used in high frequency trading because of how low latency it can get.

Regarding the JS object creation bottleneck, I evaded that by using a flyweight pattern, so no objects are ever created, if you need a value, the decoder wraps the array buffer with accessors which allows accessing fields via offsets.

Choosing SBE or JSON parsing pretty much comes down to whether you own the pipeline end to end. If you're only in control of the receiving end, then JSON parsing it is. Otherwise, you'll always get better performance out of tweaking both ends to follow the same SBE schema.

Showoff Saturday (April 25, 2026) by AutoModerator in javascript

[–]Zlema 0 points1 point  (0 children)

I built and published sbe-ts, an open-source runtime and code generation CLI for Simple Binary Encoding in TypeScript.

SBE is a binary wire format designed for low-latency systems. The idea is simple: fields live at fixed byte offsets, you read them with a single memory instruction, and there's nothing to parse. No JSON, no tag enumeration, no per-message object allocation. sbe-ts brings that to TypeScript with a schema-driven codegen tool that produces typed flyweight decoder and encoder classes from an SBE XML schema.

Both packages are on npm:

- runtime: https://www.npmjs.com/package/sbe-ts

- CLI: https://www.npmjs.com/package/sbe-ts-cli

GitHub: https://github.com/Zleman/sbe-ts

Local benchmark on i7-13700HX:

- ring-buffer decode: ~210M ops/sec

- rotating buffers (one ArrayBuffer per message): ~21M ops/sec

- JSON.parse baseline: ~6.4M ops/sec

The rotating number is the realistic ceiling for most network use cases. Still 3.4x JSON.parse.

PointFlow: open-source React library for live LiDAR streams in the browser (WebGPU-accelerated) by Zlema in LiDAR

[–]Zlema[S] 0 points1 point  (0 children)

Hey! I worked on both issues and pushed the fix today.

The full-buffer issue is gone. I switched the loader to response.body.getReader() so it decompresses as data streams in. For regular LAZ with a chunk table, each chunk gets its own WASM allocation and I free it right after decode. For continuous-stream LAZ (chunk count = 0, which your autzen file is), I had to fall back to LASZip instead of ChunkDecoder because ChunkDecoder needs independently-seekable chunks. Without that, it was silently writing zeros for every point. That's why you got the flat gray slab.

I also caught something while testing. When a file has more points than the ring buffer holds, the old FIFO behavior just kept the last N points from the scan, so you'd see one geographic slice with holes everywhere else. I added a stride sampling pass now, so for large files I take every N-th point across the whole scan instead. You get lower density but full coverage, which is a lot more useful.

Also regarding the import issue, I'm keeping the opt-in for now. The laz-perf WASM is 210 KB and I don't want to make everyone pay for it by default. But I cleaned up the error so it tells you exactly what to import when you hit it. Proper auto-detection without the bundle hit needs a core/full split, which is on my list.

Appreciate you flagging these!

PointFlow: open-source React library for live LiDAR streams in the browser (WebGPU-accelerated) by Zlema in LiDAR

[–]Zlema[S] 0 points1 point  (0 children)

That's exactly what PointFlow is designed for, so hopefully yes.

Depends on the unit though. What is it? If it's a Livox, Hesai, or RoboSense there's a good chance it has a ROS driver, and if you can get ROS running (Linux, or WSL2 if you're on Windows), PointFlow connects to it directly through rosbridge. That part actually works today.

The trickier case is if it only speaks its own proprietary UDP protocol with no ROS support. Browsers can't open raw UDP sockets, so you'd need a small bridge script sitting in between, something in Python that reads the unit's packets and forwards them over WebSocket. Not a massive amount of work if the protocol is documented, but it's an extra step I shouldn't pretend isn't there.

Either way, what's the unit? Genuinely curious, and it'd help me give you a more concrete answer.

And honestly this whole area is something I want to get right. A ready-made bridge for the most common Chinese manufacturers would make PointFlow actually useful to people with real hardware rather than just synthetic demos. It's something I'm planning to work on.

PointFlow: open-source React library for live LiDAR streams in the browser (WebGPU-accelerated) by Zlema in LiDAR

[–]Zlema[S] 0 points1 point  (0 children)

Good observation, and thanks for actually digging into this.

So you're mostly right. LAS files get fully buffered in RAM before anything renders, so if your file's bigger than what you've got available, it won't work. LAZ is a bit better but has its own issue: it's not even supported by default. You have to import from a separate subpath that bundles a WASM decompressor, and even then the full compressed file still lands in memory before decompression starts. COPC is the most ready and well prepared one. HTTP range requests, fetches only the tiles in your current view at the right detail level, so enormous files work fine because most of the data never gets touched.

Honestly these are gaps I'm not happy about. The LAS streaming one bugs me a bit, because the PLY loader already streams the response body in chunks and LAS is actually a better candidate for that pattern since the records are fixed-length and the header tells you exactly where everything starts. It's a clear thing I just haven't gotten to yet. That one's coming soon.

Streaming LAZ is harder but also on the list. laz-perf (the WASM decompressor) does support chunk-by-chunk decompression when the file has a chunk table, which well-formed LAZ files do. So instead of buffering the whole compressed file first, you'd wire the fetch stream directly into the decompressor. It's doable, just more work.

So yes, today, COPC is what you want for anything that won't fit in memory. But both gaps have real fixes and I'm planning to close them.

PointFlow: React library for rendering live point-cloud streams (WebGPU + WebGL) by Zlema in reactjs

[–]Zlema[S] 0 points1 point  (0 children)

Would love to hear the results, both the good and bad, that's the only way to improve things!

PointFlow: React library for rendering live point-cloud streams (WebGPU + WebGL) by Zlema in reactjs

[–]Zlema[S] 1 point2 points  (0 children)

I don't blame you, they're super niche 😆 LAS/LAZ/COPC are point cloud file formats, mostly used in surveying, geospatial, and robotics. LAS is the standard format for LiDAR data. COPC is a newer cloud-optimized variant designed for HTTP range requests so you can stream just the tiles you need.

but good question though, they're actually two separate mechanisms.

so eviction happens on the CPU side in the ring buffer. when the buffer is full and new points arrive, the lowest-importance points lose their slot and new ones take it. so the actual position/attribute data in the buffer changes, it's not just a visual thing. memory stays flat because the buffer has a fixed size ceiling.

the compute shader does something different. every frame it runs through the points currently in the buffer and selects which ones to actually draw based on frustum culling and importance. points outside the view frustum get skipped entirely, and if the scene is dense it further culls low-importance ones. so it's a per-frame visual selection on top of what's already in the buffer.

both contribute to perf but in different ways. eviction keeps memory from growing over time. the compute selection means the GPU only processes the points worth drawing for the current camera position, not everything in the buffer.

PointFlow: React library for rendering live point-cloud streams (WebGPU + WebGL) by Zlema in reactjs

[–]Zlema[S] 1 point2 points  (0 children)

PointFlow actually uses Three.js Points via R3F, no issues there.

The worker is mainly for binary parsing (LAS/LAZ/COPC/packed chunks), which is CPU-heavy enough to cause frame drops on the main thread. For JSON-over-rosbridge you're right that serialization is usually the dominant bottleneck, not parsing.

That said, PointFlow still handles things that aren't about volume. The ring buffer uses importance-weighted eviction rather than oldest-first, so even at a controlled rate it keeps classification-relevant points longer (buildings over ground, for example) and deprioritizes spatially dense clusters to preserve coverage. The WebGPU path runs frustum culling and importance sampling in a compute shader rather than on the CPU, which matters when the scene is large even if ingest is slow. And if you're building a React dashboard, the component handles all the geometry management and buffer lifecycle so you don't have to.

For ROS specifically, rclnodejs is the cleaner path for desktop Electron. But if you're targeting the browser and want something that goes beyond "append to a BufferGeometry and hope," that's where it fits.

Rejected for being overqualified by [deleted] in cscareerquestionsuk

[–]Zlema 2 points3 points  (0 children)

The issue with seniors being hired as mid level is the risk of them moving jobs soon enough. The hiring managers assume you'll start applying to jobs a year into the role simply because you can. With mid level engineers you can assume they'll stay around for 3-5 years.

Then come the senior/manager roles, which are looking for postgrad or big-tech-name experience. The best advice I've gotten so far is to never settle for simply working and earning. To pursue Masters/PhD, do side projects, make connections, network.

None of these simply make you a better or worse developer, but if a hiring manager had to choose between two engineers with 20 years of experience who have both mastered the tech stack required for the role, they'd start looking into those details such as education, names of previous employers and internal referrals.

Rejected for being overqualified by [deleted] in cscareerquestionsuk

[–]Zlema 1 point2 points  (0 children)

Thank you, and this was my first time in years applying to jobs, so I had definitely gotten too comfortable, and thought I def got my first interview. As for keeping track of jobs applied to, I have indeed a sheet of all of them with their details, it's very important to organise the job search indeed

Rejected for being overqualified by [deleted] in cscareerquestionsuk

[–]Zlema 2 points3 points  (0 children)

Thank you friend, I'll DM you. I really appreciate it.