io_eric
Joined 9 months ago
Joined 9 months ago
Beyond that, the 'Canvas-only' approach is computationally expensive. You are effectively re-implementing a rendering engine (layout, hit-testing, paint) on top of a rendering engine. While the browser's native DOM/CSS engine is a highly optimized, hardware-accelerated C++ powerhouse, a Canvas-only app has to manually calculate every pixel and handle every event in the WASM/JS loop. This drains battery faster and runs hotter than letting the browser do what it was built for.
Just to clarify, my benchmark was using Canvas2D, not WebGL, that's why the numbers are much lower than your WebGL2 example. Based on your comment I actually removed the command batching to test the difference, and yeah, the batching optimization is smaller than I initially thought. WebCC with batched commands hits ~100 FPS, without batching it's ~86 FPS, and Emscripten is ~40 FPS. So the batching itself only contributes about ~14 FPS.
The bigger performance difference compared to Emscripten seems to come from how Canvas2D operations are handled. Emscripten uses their val class for JS interop which wraps each canvas call in their abstraction layer. WebCC writes raw commands (opcode + arguments) directly into a buffer that the JS side decodes with a tight switch statement. The JS decoder already has direct references to canvas objects and can call methods immediately without property lookups or wrapper overhead. With 10k draw calls per frame, these small per-call differences (property access, type boxing/unboxing, generic dispatch) compound significantly.
Coi avoids this ambiguity because the compiler can definitively trace usage patterns. Since mut variables are explicitly declared, the compiler essentially just looks at where they are used in the view {} block to establish dependencies at compile time. This static analysis is precise and doesn't require the compiler to "guess" intent, effectively preserving the benefits of compile-time reactivity without the fragility found in dynamic languages
Most practical approach: AI-assisted conversion. Feed an LLM the Coi docs + your Vue code and let it transform components. For migrating existing codebases, that's likely the most efficient path.
For new code, writing Coi directly is simpler :)
The fix was adding freestanding stdint.h and stddef.h to webcc's compat layer using compiler built-ins (__SIZE_TYPE__, etc.). This makes webcc work consistently across all platforms without relying on platform-specific clang configurations.
I hope it works now for you - hit me up if there are still problems!
JSX-like view syntax – Embedding HTML with expressions, conditionals (<if>), and loops (<for>) requires parser support. Doing this with C++ macros would be unmaintainable.
Scoped CSS – The compiler rewrites selectors and injects scope attributes automatically. In WebCC, you write all styling imperatively in C++.
Component lifecycle – init{}, mount{}, tick{}, view{} blocks integrate with the reactive system. WebCC requires manual event loop setup and state management.
Efficient array rendering – Array loops track elements by key, so adding/removing/reordering items only updates the affected DOM nodes. The compiler generates the diffing and patching logic automatically.
Fine-grained reactivity – The compiler analyzes which DOM nodes depend on which state variables, generating minimal update code that only touches affected elements.
From a DX perspective: Coi lets you write <button onclick={increment}>{count}</button> with automatic reactivity. WebCC is a low-level toolkit – Coi is a high-level language that compiles to it, handling the reactive updates and DOM boilerplate automatically.
These features require a new language because they need compiler-level integration – reactive tracking, CSS scoping, JSX-like templates, and efficient array updates can't be retrofitted into C++ without creating an unmaintainable mess of macros and preprocessors. A component-based declarative language is fundamentally better suited for building UIs than imperative C++.
On the opcode parsing - the switch/case approach is intentionally simple and surprisingly fast. Modern compilers turn dense switch statements into jump tables, so it's essentially O(1) dispatch.
Your flush timing concern is understandable, but the architecture actually handles this cleanly. Buffered commands accumulate, and anything that returns a value auto-flushes first to guarantee correct ordering. For game loops, the natural pattern is batch everything during your frame logic, single flush at the end. You don't lose control, the auto-flush on sync calls ensures execution order is always maintained.
DWARF debug support is a great call
If you’ve used Vue or React, Coi will feel much more familiar. It’s a component-based model where your logic, styles, and markup live together, but you get strict static typing and native-speed performance via WASM.
The goal is a true "write once, run everywhere" workflow. On the web, it stays light by using the browser's engine. For mobile and desktop, the plan is to map that same HTML/CSS styling and component logic to a native C++/Skia backend. You get a native app's performance without losing the web's layout flexibility.