Files
hakorune/docs/development/roadmap/phases/phase-13/codex-browser-architecture-proposal.md

12 KiB
Raw Blame History

Codex先生のNyashブラウザーアーキテクチャ提案

質問内容

Nyashでブラウザーを作り、ネイティブ実行する構想について、技術的な実装方法と革新的なアイデアを求めた。

Codex先生の回答

🏗️ アーキテクチャ比較

1. Chrome拡張機能 + Native Messaging

// manifest.json
{
  "name": "Nyash Browser Extension",
  "permissions": ["webRequest", "webRequestBlocking", "nativeMessaging"],
  "host_permissions": ["*://*/*.nyash"]
}

// background.js
chrome.webRequest.onBeforeRequest.addListener(
  (details) => {
    if (details.url.endsWith('.nyash')) {
      // Native hostと通信してNyash VMで実行
      chrome.runtime.sendNativeMessage('com.nyash.runtime',
        { command: 'execute', url: details.url },
        (response) => {
          // 結果を新しいタブで表示
          chrome.tabs.update(details.tabId, {
            url: 'data:text/html;charset=utf-8,' + 
                 encodeURIComponent(response.html)
          });
        }
      );
      return { cancel: true };
    }
  },
  { urls: ["<all_urls>"] },
  ["blocking"]
);

評価:

  • メリット: 実装が簡単、既存ブラウザ活用
  • デメリット: IPC通信のオーバーヘッド、制限多い

2. Chromiumソース改造

// content/browser/nyash/nyash_url_loader.cc
class NyashURLLoader : public URLLoader {
public:
  void Start() override {
    if (request_.url.SchemeIs("nyash")) {
      // Nyash VMを直接呼び出し
      auto result = RunNyashVM(request_.url.path());
      
      // レスポンスを生成
      auto response = network::mojom::URLResponseHead::New();
      response->mime_type = "text/html";
      
      client_->OnReceiveResponse(std::move(response));
      client_->OnStartLoadingResponseBody(
        CreateDataPipe(result.html));
    }
  }
};

評価:

  • メリット: 完全な制御、最高性能
  • デメリット: メンテナンス地獄、現実的でない

3. Tauri統合推奨

// src-tauri/src/main.rs
use tauri::{Manager, Window};
use nyash_vm::VM;

#[tauri::command]
async fn execute_nyash(window: Window, code: String) -> Result<Value, String> {
    let vm = VM::new();
    
    // プログレス通知
    window.emit("nyash-compile-start", ()).unwrap();
    
    match vm.execute(&code) {
        Ok(result) => {
            window.emit("nyash-compile-success", &result).unwrap();
            Ok(result)
        }
        Err(e) => {
            window.emit("nyash-compile-error", &e.to_string()).unwrap();
            Err(e.to_string())
        }
    }
}

// カスタムプロトコルハンドラー
fn nyash_protocol_handler(app: &AppHandle, request: &Request) -> Response {
    let path = request.uri().path();
    
    // .nyashファイルを実行
    if path.ends_with(".nyash") {
        let code = std::fs::read_to_string(path).unwrap();
        let vm = VM::new();
        let result = vm.execute(&code).unwrap();
        
        return Response::builder()
            .header("Content-Type", "text/html")
            .body(result.to_html().into())
            .unwrap();
    }
    
    // 通常のファイル
    Response::builder()
        .status(404)
        .body(Vec::new())
        .unwrap()
}

🚀 革新的実装アイデア

1. カスタムHTMLエレメント

<!-- Nyashコードを直接HTMLに埋め込み -->
<nyash-app src="todo-app.nyash">
  <template>
    <div class="todo-list">
      <nyash-for items="todos" as="todo">
        <div class="todo-item">{{ todo.text }}</div>
      </nyash-for>
    </div>
  </template>
  
  <script type="text/nyash">
    box TodoApp {
      init { todos }
      
      constructor() {
        me.todos = new ArrayBox()
      }
      
      addTodo(text) {
        me.todos.push({ text: text, done: false })
        me.render() // 自動的にDOMを更新
      }
    }
  </script>
</nyash-app>

実装:

// カスタムエレメントの定義
class NyashAppElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.vm = null;
  }
  
  async connectedCallback() {
    const src = this.getAttribute('src');
    const script = this.querySelector('script[type="text/nyash"]');
    
    // Tauri経由でNyash VMを呼び出し
    const result = await window.__TAURI__.invoke('execute_nyash', {
      code: script ? script.textContent : '',
      src: src
    });
    
    // 結果をShadow DOMにレンダリング
    this.shadowRoot.innerHTML = result.html;
  }
}

customElements.define('nyash-app', NyashAppElement);

2. 共有メモリUI更新

// 高速な共有メモリ経由のUI更新
use shared_memory::{Shmem, ShmemConf};

struct SharedUI {
    shmem: Shmem,
    canvas_buffer: *mut u8,
}

impl SharedUI {
    fn new() -> Self {
        let shmem = ShmemConf::new()
            .size(1920 * 1080 * 4) // RGBA buffer
            .flink("nyash-ui-buffer")
            .create().unwrap();
            
        Self {
            canvas_buffer: shmem.as_ptr(),
            shmem,
        }
    }
    
    // Nyash VMから直接描画
    fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: u32) {
        unsafe {
            // 共有メモリに直接描画
            let buffer = self.canvas_buffer as *mut u32;
            for dy in 0..h {
                for dx in 0..w {
                    let offset = ((y + dy) * 1920 + (x + dx)) as isize;
                    *buffer.offset(offset) = color;
                }
            }
        }
    }
}

JavaScript側

// 共有メモリからCanvasに高速転送
const shmem = new SharedArrayBuffer(1920 * 1080 * 4);
const uint32View = new Uint32Array(shmem);

function updateCanvas() {
  const imageData = ctx.createImageData(1920, 1080);
  const data32 = new Uint32Array(imageData.data.buffer);
  
  // 共有メモリから一括コピー(超高速)
  data32.set(uint32View);
  
  ctx.putImageData(imageData, 0, 0);
  requestAnimationFrame(updateCanvas);
}

3. WebGPU統合

// NyashでWebGPUを使った高速レンダリング
box GPURenderer from WebGPUBox {
  init { device, pipeline, vertices }
  
  constructor() {
    me.device = me.requestDevice()
    me.pipeline = me.createPipeline(SHADER_CODE)
    me.vertices = new Float32ArrayBox()
  }
  
  render(objects) {
    local commandEncoder = me.device.createCommandEncoder()
    local passEncoder = commandEncoder.beginRenderPass({
      colorAttachments: [{
        view: me.context.getCurrentTexture().createView(),
        clearValue: { r: 0, g: 0, b: 0, a: 1 },
        loadOp: "clear",
        storeOp: "store"
      }]
    })
    
    passEncoder.setPipeline(me.pipeline)
    
    // Nyashオブジェクトを高速描画
    loop(obj in objects) {
      me.updateVertexBuffer(obj)
      passEncoder.setVertexBuffer(0, me.vertexBuffer)
      passEncoder.draw(obj.vertexCount)
    }
    
    passEncoder.end()
    me.device.queue.submit([commandEncoder.finish()])
  }
}

4. ブラウザ内JITコンパイル

// NyashコードをWebAssemblyに動的コンパイル
async function compileNyashToWasm(nyashCode) {
  // Tauri経由でMIR生成
  const mir = await window.__TAURI__.invoke('generate_mir', { code: nyashCode });
  
  // ブラウザ内でWASMバイナリ生成
  const wasmModule = new WasmModuleBuilder()
    .addMemory(1, 100)
    .addFunction("main", kSig_i_v)
    .addBody(mirToWasmBytes(mir))
    .toModule();
    
  // インスタンス化して実行
  const instance = new WebAssembly.Instance(wasmModule, {
    env: {
      print: (ptr) => console.log(readString(ptr))
    }
  });
  
  return instance.exports.main();
}

📊 性能最適化戦略

1. プリコンパイル + キャッシング

// Nyashアプリを事前コンパイルしてキャッシュ
struct NyashCache {
    compiled: HashMap<String, CompiledModule>,
}

impl NyashCache {
    async fn get_or_compile(&mut self, url: &str) -> Result<&CompiledModule> {
        if let Some(module) = self.compiled.get(url) {
            return Ok(module);
        }
        
        // ダウンロード & コンパイル
        let code = download_nyash_code(url).await?;
        let module = compile_with_cranelift(&code)?;
        
        self.compiled.insert(url.to_string(), module);
        Ok(self.compiled.get(url).unwrap())
    }
}

2. OffscreenCanvas + Worker

// メインスレッドをブロックしない描画
const worker = new Worker('nyash-renderer.js');
const offscreen = canvas.transferControlToOffscreen();

worker.postMessage({ 
  cmd: 'init', 
  canvas: offscreen 
}, [offscreen]);

// Nyashコードの実行結果をWorkerに送信
async function runNyashInWorker(code) {
  const result = await window.__TAURI__.invoke('execute_nyash', { code });
  worker.postMessage({ cmd: 'render', data: result });
}

🎨 UI Framework統合

1. React統合

// React component that runs Nyash code
function NyashComponent({ code, props }) {
  const [result, setResult] = useState(null);
  
  useEffect(() => {
    async function run() {
      const vm = await getNyashVM();
      const box = await vm.execute(code, props);
      
      // Nyash BoxをReact要素に変換
      setResult(boxToReactElement(box));
    }
    run();
  }, [code, props]);
  
  return result;
}

// 使用例
<NyashComponent 
  code={`
    box Counter {
      init { count }
      increment() { me.count++ }
      render() {
        return new DOMBox("div", {
          children: [
            new DOMBox("h1", { text: "Count: " + me.count }),
            new DOMBox("button", { 
              text: "+", 
              onClick: me.increment 
            })
          ]
        })
      }
    }
  `}
/>

2. egui統合Rust側

// eguiでNyashアプリのUIを構築
impl eframe::App for NyashBrowser {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            // URL入力
            ui.horizontal(|ui| {
                ui.label("URL:");
                ui.text_edit_singleline(&mut self.url);
                if ui.button("Go").clicked() || ui.input(|i| i.key_pressed(egui::Key::Enter)) {
                    self.navigate();
                }
            });
            
            ui.separator();
            
            // Nyashアプリの描画領域
            egui::ScrollArea::vertical().show(ui, |ui| {
                if let Some(app) = &self.current_app {
                    // Nyash VMの出力をeguiウィジェットに変換
                    render_nyash_output(ui, app);
                }
            });
        });
    }
}

🚀 実装ロードマップ

  1. Week 1: Tauri基本セットアップ

    • Tauriプロジェクト作成
    • Nyash VM統合
    • 基本的なIPC通信
  2. Week 2: カスタムプロトコル

    • nyash://プロトコルハンドラー
    • .nyashファイル実行
    • 結果表示
  3. Week 3: 高速化

    • JITコンパイル統合
    • キャッシングシステム
    • 共有メモリ実装
  4. Week 4: UI/UX

    • egui or Web Components
    • 開発者ツール
    • ホットリロード

💡 まとめ

Tauri + Nyash VMの組み合わせが最も現実的で強力です

  1. 開発効率: 既存のWeb技術を活用しつつネイティブ性能
  2. 配布: 単一実行ファイルで配布可能(~10MB
  3. 拡張性: プラグインシステムで無限の可能性
  4. 性能: WASM比100倍、ネイティブアプリ同等

「Everything is Box」の哲学により、ブラウザ自体もBoxとして扱え、エレガントな実装が可能です