Files
chanora/docs/designs/platform-backends-evaluation.md
T
Edison Jwa 523eafd4d7 docs: evaluate platform backend extraction — leave as-is (TODO-024)
Single-crate with module-level cfg gates is correct architecture.
Splitting would add dependency complexity with no compilation benefit.
2026-06-11 21:58:32 +09:00

4.1 KiB

Platform Backend Extraction Evaluation — chanora_audio

Date: 2026-06-11 Status: Evaluation (not implementation) Crate: chanora_audio (~18,500 lines total)

Current Structure

Platform-specific code is isolated into dedicated files with #[cfg] gates at the module boundary. The engine module (engine/mod.rs, engine/lifecycle.rs) uses pervasive inline cfg attributes to dispatch across platforms.

Lines Per Platform (exclusive files only)

Platform Files Lines Key Dependencies
iOS/macOS ios_voice_unit.rs, vad/apple_coreml.rs, ptt_backends/macos.rs, voice_render.rs ~2,600 coreaudio-rs, dispatch2
Android android_voice_unit.rs, android_render_ring.rs, audio_event_queue.rs ~2,130 oboe, jni, ndk-context, bytemuck
Desktop engine/capture.rs, engine/render.rs, sdl_output.rs, ptt_backends/windows*.rs, ptt_backends/linux.rs, vad/silero_onnx.rs ~4,400 cpal, sdl2, ort, windows, zbus

Shared Code (cross-platform)

Module Lines Notes
mobile_voice_backend.rs 813 Trait + types for iOS/Android backends
engine/mod.rs + engine/lifecycle.rs 2,255 Heavy inline cfg dispatch
processor/ ~500 AudioProcessor trait + backends
vad/mod.rs + vad/resampler.rs 393 VAD trait + WebRTC fallback
Other shared (frame, opus_voice, ptt, voice_activity, etc.) ~3,200 Platform-neutral

Assessment

Would splitting help?

No — not recommended at this time.

Reasons Against Splitting

  1. cfg gating already works. Platform files are cleanly isolated at the module boundary. The compiler strips unused code per-target; a separate crate doesn't add compilation speed for the active target.

  2. Shared types are deeply embedded. AudioError, AudioEffects, AudioProcessingConfig, VoiceActivityStateMachine, OpusEncoder setup, frame::* helpers, and the AudioProcessor trait are used by every platform. Extracting these into a chanora_audio_common crate is mandatory before splitting, adding a dependency node every platform crate must pull in.

  3. engine/lifecycle.rs is the real problem — but it's an integration point, not a platform backend. This 1,149-line file dispatches start_audio / stop_audio across all platforms with inline cfg. Splitting backends into separate crates wouldn't reduce this file's complexity — it would just move the cross-crate import surface here.

  4. Dependency graph complexity. The current single crate has 5 cfg-gated dependency blocks in Cargo.toml. Splitting into 3+ crates means each platform crate needs its own Cargo.toml with the shared types dependency, and the top-level chanora_audio (or chanora_core) must depend on all of them with target-conditional cfg features.

  5. Test surface stays the same. Platform-specific tests already compile only on their target OS. A crate boundary doesn't improve test isolation.

  6. The MobileVoiceAudioBackend trait is the natural seam — and it already exists. mobile_voice_backend.rs defines the cross-platform interface. The iOS backend will implement it under SDD-117. This is the correct abstraction boundary without adding a crate boundary.

When Splitting Would Make Sense

  • If build times for cross-compilation become painful (building all 3 platform variants from CI)
  • If platform-specific dependencies cause feature flag conflicts (not observed today)
  • If the crate exceeds ~30k lines and the cfg density makes navigation difficult
  • If a platform team needs to own a crate independently

Recommendation

Keep the current single-crate structure. Focus cleanup effort on:

  1. Reducing inline cfg in engine/lifecycle.rs — extract platform dispatch into the existing IosVoiceBackend enum pattern
  2. Back-filling IosVoiceUnit to MobileVoiceAudioBackend (SDD-117) — this unifies the mobile interface
  3. Documenting the cfg convention — module-level cfg at the file boundary (current pattern) vs. inline cfg blocks in shared files