この記事の内容はでの通信の実装方法(コード付き)に関するもので、ある程度の参考価値はありますので、困っている方は参考にしていただければ幸いです。
通信メソッド
deno の実行コードはノードと同様で、同期メソッドと非同期メソッドが含まれます。非同期メソッドは Promise.then によって実装されます。
Typescript/JavaScript は Rust を呼び出します
前のセクションでは、deno の開始時に v8 分離インスタンスが初期化されると述べました。関数は v8 isolate のインスタンスにバインドされており、v8 が Javascript コードを実行するとき、これらのバインドされた関数は Javascript 関数のように呼び出すことができます。具体的なバインディングの実装は次のとおりです。
void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context) { v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); auto global = context->Global(); auto deno_val = v8::Object::New(isolate); CHECK(global->Set(context, deno::v8_str("libdeno"), deno_val).FromJust()); auto print_tmpl = v8::FunctionTemplate::New(isolate, Print); auto print_val = print_tmpl->GetFunction(context).ToLocalChecked(); CHECK(deno_val->Set(context, deno::v8_str("print"), print_val).FromJust()); auto recv_tmpl = v8::FunctionTemplate::New(isolate, Recv); auto recv_val = recv_tmpl->GetFunction(context).ToLocalChecked(); CHECK(deno_val->Set(context, deno::v8_str("recv"), recv_val).FromJust()); auto send_tmpl = v8::FunctionTemplate::New(isolate, Send); auto send_val = send_tmpl->GetFunction(context).ToLocalChecked(); CHECK(deno_val->Set(context, deno::v8_str("send"), send_val).FromJust()); auto eval_context_tmpl = v8::FunctionTemplate::New(isolate, EvalContext); auto eval_context_val = eval_context_tmpl->GetFunction(context).ToLocalChecked(); CHECK(deno_val->Set(context, deno::v8_str("evalContext"), eval_context_val) .FromJust()); auto error_to_json_tmpl = v8::FunctionTemplate::New(isolate, ErrorToJSON); auto error_to_json_val = error_to_json_tmpl->GetFunction(context).ToLocalChecked(); CHECK(deno_val->Set(context, deno::v8_str("errorToJSON"), error_to_json_val) .FromJust()); CHECK(deno_val->SetAccessor(context, deno::v8_str("shared"), Shared) .FromJust()); }
バインディングが完了したら、次のコードを使用して c メソッドと Typescript メソッド間のマッピングを Typescript に実装できます
libdeno.ts
interface Libdeno { recv(cb: MessageCallback): void; send(control: ArrayBufferView, data?: ArrayBufferView): null | Uint8Array; print(x: string, isErr?: boolean): void; shared: ArrayBuffer; /** Evaluate provided code in the current context. * It differs from eval(...) in that it does not create a new context. * Returns an array: [output, errInfo]. * If an error occurs, `output` becomes null and `errInfo` is non-null. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any evalContext(code: string): [any, EvalErrorInfo | null]; errorToJSON: (e: Error) => string; } export const libdeno = window.libdeno as Libdeno;
Typescript コードを実行するときは、libdeno を導入して c メソッドを直接呼び出すだけです。例:
import { libdeno } from "./libdeno"; function sendInternal( builder: flatbuffers.Builder, innerType: msg.Any, inner: flatbuffers.Offset, data: undefined | ArrayBufferView, sync = true ): [number, null | Uint8Array] { const cmdId = nextCmdId++; msg.Base.startBase(builder); msg.Base.addInner(builder, inner); msg.Base.addInnerType(builder, innerType); msg.Base.addSync(builder, sync); msg.Base.addCmdId(builder, cmdId); builder.finish(msg.Base.endBase(builder)); const res = libdeno.send(builder.asUint8Array(), data); builder.inUse = false; return [cmdId, res]; }
libdeno.send メソッドを呼び出してデータを c に渡し、次に c を使用して呼び出します。特定のプロジェクトを実装するためのRustコードが動作します。
同期
Typescript では、sendInternal メソッドの sync パラメータを true に設定するだけですが、Rust では判定されます。 sync パラメータに基づいて同期または非同期操作を実行します。sync が true の場合、libdeono.send メソッドは実行結果を返します。Rust と TypeScript の間でデータを転送するには、データをシリアル化する必要があります。フラットバッファ ライブラリは、ここでシリアル化操作を行います。
const [cmdId, resBuf] = sendInternal(builder, innerType, inner, data, true);
非同期実装
同様に、非同期メソッドを実装するには、sync パラメータを false に設定するだけで済みます。ただし、同期と比較して、非同期操作には、非同期通信を実行する際のフォールバック メソッドが多数あります。オペレーションが呼び出されると、libdeno.send メソッドはオペレーションを識別する一意の cmdId を返します。同時に、非同期通信が完了すると、cmdId をキー、promise を値として使用して、promise オブジェクトが作成され、マップに追加されます。コードは次のとおりです。
const [cmdId, resBuf] = sendInternal(builder, innerType, inner, data, false); util.assert(resBuf == null); const promise = util.createResolvable<msg.Base>(); promiseTable.set(cmdId, promise); return promise;
Typescript で libdeno.send メソッドが呼び出されるとき、C ファイル binding.cc の Send メソッドが呼び出されます。 deno で初期化されています。v8 isolate にバインドされています。 Send メソッドでは、ops.rs ファイル内のdispatch メソッドが呼び出され、メッセージと関数のマッピングが実装されます。各メッセージは機能に対応しており、例えばファイル読み取りメッセージはファイル読み取り機能に対応する。
pub fn dispatch( isolate: &Isolate, control: libdeno::deno_buf, data: libdeno::deno_buf, ) -> (bool, Box<Op>) { let base = msg::get_root_as_base(&control); let is_sync = base.sync(); let inner_type = base.inner_type(); let cmd_id = base.cmd_id(); let op: Box<Op> = if inner_type == msg::Any::SetTimeout { // SetTimeout is an exceptional op: the global timeout field is part of the // Isolate state (not the IsolateState state) and it must be updated on the // main thread. assert_eq!(is_sync, true); op_set_timeout(isolate, &base, data) } else { // Handle regular ops. let op_creator: OpCreator = match inner_type { msg::Any::Accept => op_accept, msg::Any::Chdir => op_chdir, msg::Any::Chmod => op_chmod, msg::Any::Close => op_close, msg::Any::FetchModuleMetaData => op_fetch_module_meta_data, msg::Any::CopyFile => op_copy_file, msg::Any::Cwd => op_cwd, msg::Any::Dial => op_dial, msg::Any::Environ => op_env, msg::Any::Exit => op_exit, msg::Any::Fetch => op_fetch, msg::Any::FormatError => op_format_error, msg::Any::Listen => op_listen, msg::Any::MakeTempDir => op_make_temp_dir, msg::Any::Metrics => op_metrics, msg::Any::Mkdir => op_mkdir, msg::Any::Open => op_open, msg::Any::ReadDir => op_read_dir, msg::Any::ReadFile => op_read_file, msg::Any::Readlink => op_read_link, msg::Any::Read => op_read, msg::Any::Remove => op_remove, msg::Any::Rename => op_rename, msg::Any::ReplReadline => op_repl_readline, msg::Any::ReplStart => op_repl_start, msg::Any::Resources => op_resources, msg::Any::Run => op_run, msg::Any::RunStatus => op_run_status, msg::Any::SetEnv => op_set_env, msg::Any::Shutdown => op_shutdown, msg::Any::Start => op_start, msg::Any::Stat => op_stat, msg::Any::Symlink => op_symlink, msg::Any::Truncate => op_truncate, msg::Any::WorkerGetMessage => op_worker_get_message, msg::Any::WorkerPostMessage => op_worker_post_message, msg::Any::Write => op_write, msg::Any::WriteFile => op_write_file, msg::Any::Now => op_now, msg::Any::IsTTY => op_is_tty, msg::Any::Seek => op_seek, msg::Any::Permissions => op_permissions, msg::Any::PermissionRevoke => op_revoke_permission, _ => panic!(format!( "Unhandled message {}", msg::enum_name_any(inner_type) )), }; op_creator(&isolate, &base, data) }; // ...省略多余的代码 }
各タイプの関数では、Typescript の libdeo.send メソッドを呼び出すときに渡される sync パラメーターの値に基づいて、同期実行か非同期実行が決定されます。
let (is_sync, op) = dispatch(isolate, control_buf, zero_copy_buf);
同期実行
dispatch メソッドを実行すると、is_sync 変数が返されます。is_sync が true の場合、メソッドが同期的に実行されることを意味し、op は返された結果を意味します。 Rust コードは、C ファイル api.cc の deno_respond メソッドを呼び出して、実行結果を同期します。deno_respond メソッドは、current_args_ の値に基づいて同期メッセージであるかどうかを判断します。current_args_ に値がある場合、結果は直接返されます。 。
非同期実行
denoでは、非同期オペレーションの実行はRustのTokioモジュールを通じて実装されており、ディスパッチメソッドを呼び出した後、それが非同期オペレーションの場合、is_syncの値はfalseになります。 、そして操作はそうではありません。 次に、実行結果がありますが、実行関数があります。 tokio モジュールを使用して、関数を非同期に実行するスレッドを派生します。
let task = op .and_then(move |buf| { let sender = tx; // tx is moved to new thread sender.send((zero_copy_id, buf)).expect("tx.send error"); Ok(()) }).map_err(|_| ()); tokio::spawn(task);
deno が初期化されると、パイプラインが作成されます。コードは次のとおりです:
let (tx, rx) = mpsc::channel::<(usize, Buf)>();
非同期操作により実行用の新しいスレッドが作成されるため、パイプラインは異なるスレッド間の通信を実現できます。そのため、サブスレッドはメインスレッドと直接通信できず、パイプラインメカニズムを通じて実装する必要があります。非同期コードの実行が完了したら、tx.send メソッドを呼び出して実行結果をパイプラインに追加します。イベント ループは毎回結果をパイプラインから読み取り、返します。
非同期操作はイベント ループに依存しているため、まず deno のイベント ループについて説明します。実際、イベント ループは非常に単純です。実行されるコードの一部です。条件が満たされると、イベント ループは実行を終了します。deno のメイン イベント ループ コードは次のように実装されます:
pub fn event_loop(&self) -> Result<(), JSError> { // Main thread event loop. while !self.is_idle() { match recv_deadline(&self.rx, self.get_timeout_due()) { Ok((zero_copy_id, buf)) => self.complete_op(zero_copy_id, buf), Err(mpsc::RecvTimeoutError::Timeout) => self.timeout(), Err(e) => panic!("recv_deadline() failed: {:?}", e), } self.check_promise_errors(); if let Some(err) = self.last_exception() { return Err(err); } } // Check on done self.check_promise_errors(); if let Some(err) = self.last_exception() { return Err(err); } Ok(()) }
self.is_idle メソッドは、すべての非同期操作が実行されているかどうかを判断するために使用されます。すべての非同期操作が完了したら、イベント ループを停止します。is_idle メソッドのコードは次のとおりです:
fn is_idle(&self) -> bool { self.ntasks.get() == 0 && self.get_timeout_due().is_none() }
非同期メソッド呼び出しが生成されると、次のメソッドが呼び出されて内部値が増加します。
fn ntasks_increment(&self) { assert!(self.ntasks.get() >= 0); self.ntasks.set(self.ntasks.get() + 1); }
イベント ループでは、まずパイプラインから値が取得されます。ここで、イベント ループはコンシューマとして機能し、非同期メソッドを実行するサブスレッドはコンシューマとして機能します。プロデューサー。イベント ループで実行結果が得られると、ntasks_decrement メソッドが呼び出され、ntasks の内部値が 1 減算されます。ntasks の値が 0 になると、イベント ループは実行を終了します。各ループでは、パイプラインで取得した値をパラメータとして使用し、complete_op メソッドを呼び出し、結果を返します。
在初始化v8实例时,绑定的c++方法中有一个Recv方法,该方法的作用时暴露一个Typescript的函数给rust,在deno的io.ts文件的start方法中执行libdeno.recv(handleAsyncMsgFromRust),将handleAsyncMsgFromRust函数通过c++方法暴露给rust。具体实现如下:
export function start(source?: string): msg.StartRes { libdeno.recv(handleAsyncMsgFromRust); // First we send an empty `Start` message to let the privileged side know we // are ready. The response should be a `StartRes` message containing the CLI // args and other info. const startResMsg = sendStart(); util.setLogDebug(startResMsg.debugFlag(), source); setGlobals(startResMsg.pid(), startResMsg.noColor(), startResMsg.execPath()!); return startResMsg; }
当异步操作执行完成后,可以在rust中直接调用handleAsyncMsgFromRust方法,将结果返回给Typescript。先看一下handleAsyncMsgFromRust方法的实现细节:
export function handleAsyncMsgFromRust(ui8: Uint8Array): void { // If a the buffer is empty, recv() on the native side timed out and we // did not receive a message. if (ui8 && ui8.length) { const bb = new flatbuffers.ByteBuffer(ui8); const base = msg.Base.getRootAsBase(bb); const cmdId = base.cmdId(); const promise = promiseTable.get(cmdId); util.assert(promise != null, `Expecting promise in table. ${cmdId}`); promiseTable.delete(cmdId); const err = errors.maybeError(base); if (err != null) { promise!.reject(err); } else { promise!.resolve(base); } } // Fire timers that have become runnable. fireTimers(); }
从代码handleAsyncMsgFromRust方法的实现中可以知道,首先通过flatbuffer反序列化返回的结果,然后获取返回结果的cmdId,根据cmdId获取之前创建的promise对象,然后调用promise.resolve方法触发promise.then中的代码执行。
以上がdeno通信の実装方法(コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。