diff --git a/ffi/justfile b/ffi/justfile index df316dcfa..bd5c7936b 100644 --- a/ffi/justfile +++ b/ffi/justfile @@ -8,10 +8,8 @@ set shell := ["bash", "-euo", "pipefail", "-c"] export PATH := if env("RUST2_TS_TOOLCHAIN", "") == "" { env("PATH") } else { env("RUST2_TS_TOOLCHAIN") / "bin:" + env("PATH") } -# The moq revision these recipes export by default, behind `just ts moq`'s: moq's later API -# needs moq/ffi.toml to follow it, and past moq-dev/moq#7032 moq-tokio's `Send` check -# overflows the recursion limit `moq-export` denies. -moq_rev := "30f25c04e6d0288d87893dc609ba582518be2cf6" +# The moq revision these recipes export by default: `just ts`'s, so both follow one moq. +moq_rev := shell(just_executable() + " --justfile ../ts/justfile --evaluate moq_rev") default: @just --list @@ -241,7 +239,7 @@ standin checkout="": dir=target/moq-ffi # moq-ffi's dependencies among the exports, moq-tokio with its default features as # moq-ffi has them, and moq-pattern and kio, which moq-ffi reaches through moq-net. - exports=(hang kio moq-clock moq-mux moq-net moq-pattern moq-tokio) + exports=(hang kio moq-mux moq-net moq-pattern moq-tokio) mkdir -p "$dir/src" { printf '# Generated by just ffi standin, from %s.\n' "$src" @@ -258,7 +256,7 @@ standin checkout="": # moq's lockfile pins what moq's own builds use. cp "$src/Cargo.lock" "$dir/Cargo.lock" -# Check out the pinned moq revision in target/moq, apart from `just ts`'s so each builds its own pin, copying the objects its clone already has. +# Check out the pinned moq revision in target/moq, apart from `just ts`'s so neither recipe touches the other's checkout, copying the objects its clone already has. [private] moq-pin: [ -d target/moq/.git ] || git clone --quiet --filter=blob:none --reference-if-able ../ts/target/moq --dissociate https://github.com/moq-dev/moq target/moq diff --git a/ffi/moq/bench/moq-c.c b/ffi/moq/bench/moq-c.c index 881953bf7..d1c5ef702 100644 --- a/ffi/moq/bench/moq-c.c +++ b/ffi/moq/bench/moq-c.c @@ -35,7 +35,8 @@ int main(void) { for (int g = 0; g < GROUPS; g++) { int32_t group = moq_publish_track_group((uint32_t)track); for (int f = 0; f < FRAMES; f++) { - if (moq_publish_group_frame((uint32_t)group, payload, sizeof payload, micros++) < 0) { + moq_timestamp at = {.value = micros++, .scale = 1000000}; + if (moq_publish_group_frame((uint32_t)group, payload, sizeof payload, &at) < 0) { return 2; } } diff --git a/ffi/moq/ffi.toml b/ffi/moq/ffi.toml index 61c3b5b36..fd792601e 100644 --- a/ffi/moq/ffi.toml +++ b/ffi/moq/ffi.toml @@ -13,8 +13,9 @@ exclude = [ "DecodeError", "Driver", "IntoBytes", - "InvalidEpoch", + "InvalidInstance", "Path::from_static", + "Path::parts", "Server::accept", "Server::accept_request", "Timed", @@ -25,7 +26,10 @@ exclude = [ "announce::Announce", "announce::Consumer::poll_synced", "auth::EmptyToken", + "broadcast::Lister::poll_unwanted", + "broadcast::Lister::poll_wanted", "bytes", + "datagram::Producer::poll_priority", "frame::Buffer", "frame::Producer::group", "goaway::Consumer::poll", @@ -39,8 +43,6 @@ exclude = [ "track::Resolving", "track::Subscribing", "transport::Error", - "transport::MaybeSend", - "transport::MaybeSync", "transport::memory::Chunks", "transport::memory::SendStream", "transport::poll::AcceptBi", @@ -74,33 +76,13 @@ exclude = [ "session::Stats" = "session_stats_snapshot" # The rule makes both this type and `Path::relative` `moq_path_relative`. "path::Relative" = "relative" +# The rule makes both this enum and `auth::Request::kind` `moq_auth_request_kind`. +"auth::RequestKind" = "request_kind" -# Exported with moq-net, whose signatures name their types, such as `Patterns` and `Instant`. +# Exported with moq-net, whose signatures name its types, such as `Patterns`. [crates.moq-pattern] exclude = ["Pattern::captures"] -[crates.moq-clock] -exclude = [ - "Deadline::poll", - "Deadlines::enter", - "Elapsed", - "Entered", - "Instant::from_std", - "Instant::into_std", - "OutOfRange", - "Sleep", - "Timeout", - "drive", - "timeout", - "timeout_at", -] - -[crates.moq-clock.rename] -# The rule makes both this function and the type `Wall` `moq_wall`. -"wall" = "now_wall" -# The rule makes both this function and the type `Interval` `moq_interval`. -"interval" = "interval_every" - [crates.moq-tokio] exclude = [ "Addrs", @@ -123,6 +105,7 @@ exclude = [ "cli::answer", "cli::check_env", "cli::usage_rs", + "congestion", "connect::Addr::pinned", "connection::Monitor::poll_presence", "crypto::install_default", @@ -132,40 +115,37 @@ exclude = [ "notify", "panic::isolate", "panic::message", + "quic::Controller::0", "quic::Error", + "quic::demux::Counters", + "quic::demux::Datagram::local", + "quic::demux::Demux::counters", + "quic::demux::Discard", + "quic::demux::Pin", + "quic::demux::Transmit", + "quic::demux::WebRtc::pin", + "quic::demux::WebRtc::poll_send", + "quic::demux::WebRtc::send", "quic::driver::ApplicationClose", - "quic::driver::Chunk", - "quic::driver::ClosedStream", "quic::driver::ConfigError", "quic::driver::ConnectionClose", "quic::driver::ConnectionId", "quic::driver::ConnectionIdGenerator", "quic::driver::CreditStats", - "quic::driver::Dir", "quic::driver::EcnCodepoint", "quic::driver::FrameStats", "quic::driver::FrameType", "quic::driver::InvalidCid", - "quic::driver::Lease", "quic::driver::Limits", "quic::driver::LimitsError", "quic::driver::PathStats", - "quic::driver::ReadError", - "quic::driver::ReadExactError", - "quic::driver::ReadToEndError", - "quic::driver::RecvStream", - "quic::driver::ResetError", - "quic::driver::SendStream", - "quic::driver::StoppedError", - "quic::driver::StreamId", + "quic::driver::Rate", "quic::driver::Transmit", "quic::driver::TransportErrorCode", "quic::driver::UdpStats", "quic::driver::VarInt", "quic::driver::VarIntBoundsExceeded", "quic::driver::Version", - "quic::driver::WriteError", - "quic::driver::Written", "quic::driver::client", "quic::driver::connection::Connecting", "quic::driver::connection::Connection::accept_bi", @@ -178,12 +158,17 @@ exclude = [ "quic::driver::connection::Connection::open_bi", "quic::driver::connection::Connection::open_uni", "quic::driver::connection::Connection::peer_identity", + "quic::driver::connection::Connection::poll_accept_bi", + "quic::driver::connection::Connection::poll_accept_uni", + "quic::driver::connection::Connection::poll_closed", + "quic::driver::connection::Connection::poll_open_bi", + "quic::driver::connection::Connection::poll_open_uni", + "quic::driver::connection::Connection::poll_recv_datagram", "quic::driver::connection::Connection::read_datagram", "quic::driver::connection::Connection::send_datagram", "quic::driver::connection::Connection::set_limits", "quic::driver::connection::Connection::stats", "quic::driver::connection::Error", - "quic::driver::connection::SendDatagramError", "quic::driver::connection::Stats", "quic::driver::crypto", "quic::driver::endpoint::AsyncUdpSocket", @@ -194,14 +179,49 @@ exclude = [ "quic::driver::endpoint::Error", "quic::driver::endpoint::Incoming", "quic::driver::endpoint::Socket", + "quic::driver::recv::Error", + "quic::driver::recv::Lease", + "quic::driver::recv::Stream", + "quic::driver::send::Error", + "quic::driver::send::Finished", + "quic::driver::send::Offset", + "quic::driver::send::Options::group", + "quic::driver::send::Stream", "quic::driver::server", + "quic::driver::stream", "quic::driver::transport", + "quic::web::ConnectError", + "quic::web::Request::reject", + "quic::web::Request::respond", + "quic::web::Session::poll_open_request", + "quic::web::Session::poll_recv_datagram", + "quic::web::Session::request", + "quic::web::Session::response", + "quic::web::Session::stats", + "quic::web::WebTransportError", + "quic::web::http", + "quic::web::proto", + "quic::web::recv::Stream::id", + "quic::web::recv::Stream::poll_lease", + "quic::web::recv::Stream::poll_read", + "quic::web::recv::Stream::poll_reset", + "quic::web::recv::Stream::read", + "quic::web::recv::Stream::read_exact", + "quic::web::send::Finished", + "quic::web::send::Stream::id", + "quic::web::send::Stream::poll_closed", + "quic::web::send::Stream::set_group", + "quic::web::send::Stream::write", "rustls", "secret::Secret::as_bytes", "server::Config::websocket", + "server::Request::with_quic_limits", "server::Request::within", "tcp::Error", "tcp::Listener", + "tcp::TlsAcceptor::accept", + "tcp::TlsAcceptor::config", + "tcp::TlsAcceptor::offered", "tls::Connect::build", "tls::Identity::generate", "tls::Listen::load_roots", @@ -211,10 +231,6 @@ exclude = [ "tls::Serve::config", "tls::Serve::resolver", "tls::parse_fingerprint", - "transport::Error", - "transport::RecvStream", - "transport::SendStream", - "transport::Session", "unix::Error", "watch::Files", "websocket::Error", @@ -244,6 +260,7 @@ exclude = [ [crates.hang] exclude = [ + "Catalog::contributions", "Catalog::from_reader", "Catalog::subscribe", "catalog::Audio::enabled", @@ -287,6 +304,11 @@ exclude = [ # `moq_catalog__codec_kind`. "catalog::AudioCodecKind" = "audio_codec_kind" "catalog::VideoCodecKind" = "video_codec_kind" +# The rule makes both this method and the field `media` `moq_hang_catalog_media`. A rename +# by the generic path would name every instance's alike. +"Catalog::media" = "hang_catalog_to_media" +"Catalog::media" = "hang_catalog_extra_to_media" +"Catalog::media" = "hang_catalog_ext_to_media" [crates.moq-mux] exclude = [ @@ -338,6 +360,8 @@ exclude = [ "container::fmp4::mp4_atom", "container::mkv::Error", "container::mkv::Export", + "container::mp4::Export", + "container::mp4::repair", "container::ts::Catalog", "container::ts::Export", "container::ts::Follower", @@ -347,11 +371,18 @@ exclude = [ "container::ts::MultipleProgramsError", "container::ts::Programs::decode", "container::ts::Programs::finish", + "container::ts::Restart", "container::ts::stats::Export::streams", "container::ts::stats::Snapshot::streams", "follow::Event", "follow::Instances", "json::Consumer", + "select::Selected::audio", + "select::Selected::video", + "timeline::Mapping", + "timeline::Resume", + "timeline::Track::apply", + "timeline::Track::resume", ] [crates.moq-mux.rename] @@ -362,7 +393,9 @@ exclude = [ "catalog::Consumer" = "mux_catalog_consumer" # The rule makes both this constant and `CatalogFormat`'s `Default` `moq_catalog_catalog_format_default`. "catalog::CatalogFormat::DEFAULT" = "catalog_catalog_format_fallback" -# The rule makes both this constant and the method `Clock::timescale` `moq_clock_timescale`. +# The rule makes this media clock's methods, such as `now`, kio's clock's: `moq_clock_now`. +"Clock" = "mux_clock" +# The rule makes both this constant and the method `Clock::timescale` `moq_mux_clock_timescale`. "Clock::TIMESCALE" = "clock_fixed_timescale" # The rule makes each of these functions and its module's `Config` `moq_codec__config`. "codec::aac::config" = "codec_aac_audio_config" @@ -372,7 +405,8 @@ exclude = [ "container::fmp4::Chunk::init" = "container_fmp4_chunk_into_init" "container::fmp4::Chunk::fragment" = "container_fmp4_chunk_into_fragment" -# The waiter a driver's poll registers, which C makes from its waker and holds across polls. +# The waiter a driver's poll registers, which C makes from its waker and holds across polls, +# and the clock it polls at. [crates.kio] exclude = [ "Bridge", @@ -381,7 +415,6 @@ exclude = [ "ConsumerWeak", "Lock", "LockGuard", - "MaybeSend", "Mut", "Park::hold", "Pending", @@ -392,17 +425,41 @@ exclude = [ "Ref", "Shared", "Task", + "TaskKey", "Tasks", - "Unused", + "Unwanted", "Waiter::batch", "Waiter::context", "Waiter::poll_future", "Waiter::waker", + "Watch", "Weak", + "clock::Deadline::poll", + "clock::Deadlines::enter", + "clock::Elapsed", + "clock::Entered", + "clock::Instant::from_std", + "clock::Instant::into_std", + "clock::OutOfRange", + "clock::Rng", + "clock::Sleep", + "clock::Timeout", + "clock::drive", + "clock::rng", + "clock::timeout", + "clock::timeout_at", "coop::Budget::poll_yield", "log::Evicted", "log::Read", "log::Reader", "log::Writer", + "machine", "wait", + "wrapper", ] + +[crates.kio.rename] +# The rule makes both this function and the type `clock::Wall` `moq_clock_wall`. +"clock::wall" = "clock_now_wall" +# The rule makes both this function and the type `clock::Interval` `moq_clock_interval`. +"clock::interval" = "clock_interval_every" diff --git a/ffi/moq/moq.c b/ffi/moq/moq.c index 7aedb56e4..65046e576 100644 --- a/ffi/moq/moq.c +++ b/ffi/moq/moq.c @@ -1,6 +1,6 @@ /* moq's exported C API against the real library: a broadcast's track, published and * subscribed to in one process, with every read polled as a future, and an origin. - * moq-pattern, moq-clock, moq-tokio, hang, and moq-mux export into the same library, and + * moq-pattern, kio, moq-tokio, hang, and moq-mux export into the same library, and * each names the others' types, the catalog is lent to C for a call, and a session's * config is filled in field by field. * @@ -207,12 +207,12 @@ int main(void) { moq_group_consumer_read_frame_future_free(reading); CHECK(frame != NULL && is(moq_frame_frame_payload(frame), "hello")); CHECK(moq_frame_frame_timestamp(frame) == NULL); - /* When it arrived: moq-clock's `Instant`, from moq-clock's own export. */ - moq_instant *arrived = moq_frame_frame_arrived(frame); - moq_instant *now = moq_now(); - CHECK(moq_instant_checked_duration_since(now, arrived).some); - moq_instant_free(now); - moq_instant_free(arrived); + /* When it arrived: kio's clock's `Instant`, from kio's own export. */ + moq_clock_instant *arrived = moq_frame_frame_arrived(frame); + moq_clock_instant *now = moq_clock_now(); + CHECK(moq_clock_instant_checked_duration_since(now, arrived).some); + moq_clock_instant_free(now); + moq_clock_instant_free(arrived); moq_frame_frame_free(frame); /* A timestamp is a `Copy` value behind a handle: passing it copies it out. The track @@ -299,21 +299,21 @@ int main(void) { /* Paths own their segments, so C builds them, and relative ones, from a string. */ moq_path *cam = NULL; - moq_path_invalid path_invalid; - CHECK(!moq_path_new(str("/cam/"), &cam, &path_invalid) && path_invalid == MOQ_PATH_INVALID_EMPTY_SEGMENT); - CHECK(moq_path_new(str("cam"), &cam, &path_invalid)); + moq_path_error path_error; + CHECK(!moq_path_new(str("/cam/"), &cam, &path_error) && path_error == MOQ_PATH_ERROR_EMPTY_SEGMENT); + CHECK(moq_path_new(str("cam"), &cam, &path_error)); CHECK(is(moq_path_as_str(cam), "cam")); moq_path *joined = NULL; - CHECK(moq_path_join(path, cam, &joined, &path_invalid)); + CHECK(moq_path_join(path, cam, &joined, &path_error)); CHECK(is(moq_path_as_str(joined), "room/alice/cam")); moq_path *stripped = moq_path_strip_prefix(joined, path); CHECK(stripped != NULL && moq_path_eq(stripped, cam)); moq_path_free(stripped); CHECK(moq_path_strip_prefix(path, joined) == NULL); moq_relative *up = NULL; - CHECK(moq_relative_new(str("../bob"), &up, &path_invalid)); + CHECK(moq_relative_new(str("../bob"), &up, &path_error)); moq_path *resolved = NULL; - CHECK(moq_path_resolve(joined, up, &resolved, &path_invalid)); + CHECK(moq_path_resolve(joined, up, &resolved, &path_error)); CHECK(is(moq_path_as_str(resolved), "room/bob")); moq_path_free(resolved); moq_relative_free(up); @@ -327,11 +327,11 @@ int main(void) { * made from the waker and held across polls, since what a poll registers lives only * as long as its waiter. It runs until every owner of the origin is gone. */ moq_waiter *waiter = moq_waiter_new(waker); - now = moq_now(); - moq_instant *deadline = NULL; + now = moq_clock_now(); + moq_clock_instant *deadline = NULL; CHECK(moq_origin_driver_poll(driver, now, waiter, &deadline, &err)); - moq_instant_free(deadline); - moq_instant_free(now); + moq_clock_instant_free(deadline); + moq_clock_instant_free(now); /* A client publishes the origin through moq-net's generic `with_publisher`, at each * type that implements its bound, by reference, so C keeps its handle. */ moq_client *client = moq_client_new(); @@ -343,14 +343,14 @@ int main(void) { moq_origin_producer_free(origin); /* The last owner's drop wakes the driver, which then finishes, cleanly. */ CHECK(wakes > before); - now = moq_now(); + now = moq_clock_now(); deadline = NULL; err = NULL; CHECK(!moq_origin_driver_poll(driver, now, waiter, &deadline, &err)); CHECK(deadline == NULL && err != NULL && is(moq_error_to_string(err), "closed")); moq_error_free(err); err = NULL; - moq_instant_free(now); + moq_clock_instant_free(now); moq_waiter_free(waiter); moq_origin_driver_free(driver); @@ -421,9 +421,12 @@ int main(void) { CHECK(listed_stats == 1); /* A JSON track the catalog lists, published through moq-mux's generic `json_snapshot` * at a JSON document, and a section C parses from text, set in the lent catalog and - * read back as text. */ + * read back as text. Its track has a timescale, since every write is stamped. */ + moq_timescale *milli = moq_timescale_milli(); + moq_track_info *status_info = moq_track_info_with_timescale(moq_track_info_default(), milli); + moq_timescale_free(milli); moq_track_producer *status = NULL; - CHECK(moq_broadcast_producer_create_track(publisher, str("status"), NULL, &status, &err)); + CHECK(moq_broadcast_producer_create_track(publisher, str("status"), status_info, &status, &err)); moq_json_config *json_config = moq_json_config_default(); moq_json_options *json_options = moq_json_options_default(); moq_json_snapshot_json *status_snapshot = NULL; @@ -439,7 +442,9 @@ int main(void) { moq_json *latest = NULL; moq_buf json_err; CHECK(moq_json_parse(str("{\"on\": true}"), &latest, &json_err)); - CHECK(moq_json_snapshot_json_update(status_snapshot, latest, &mux_err)); + moq_timestamp *stamp = moq_timestamp_now(); + CHECK(moq_json_snapshot_json_update(status_snapshot, latest, stamp, &mux_err)); + moq_timestamp_free(stamp); moq_json_free(latest); moq_track_subscriber *status_subscriber = moq_json_snapshot_json_consume(status_snapshot); moq_track_subscriber_recv_group_future *status_receiving = moq_track_subscriber_recv_group(status_subscriber); @@ -655,10 +660,10 @@ int main(void) { moq_connection_established_future_free(establishing); CHECK(established != NULL); - moq_origin_consumer_request_broadcast_future *requesting = moq_origin_consumer_request_broadcast(remote, str("live"), NULL); + moq_origin_consumer_request_future *requesting = moq_origin_consumer_request(remote, str("live"), NULL); moq_broadcast_consumer *remote_broadcast = NULL; - AWAIT(moq_origin_consumer_request_broadcast_poll(requesting, session_waker, &remote_broadcast, &err)); - moq_origin_consumer_request_broadcast_future_free(requesting); + AWAIT(moq_origin_consumer_request_poll(requesting, session_waker, &remote_broadcast, &err)); + moq_origin_consumer_request_future_free(requesting); CHECK(remote_broadcast != NULL); moq_track_consumer *remote_track = NULL; CHECK(moq_broadcast_consumer_track(remote_broadcast, str("video"), &remote_track, &err)); diff --git a/ffi/moq/moq.cpp b/ffi/moq/moq.cpp index 77771d0e7..15be5ab3a 100644 --- a/ffi/moq/moq.cpp +++ b/ffi/moq/moq.cpp @@ -1,6 +1,6 @@ // moq.c's script through the generated C++ header: a broadcast's track, published and // subscribed to in one process, with every read polled as a future, and an origin, with -// moq-pattern, moq-clock, moq-tokio, hang, and moq-mux exported into the same library, +// moq-pattern, kio, moq-tokio, hang, and moq-mux exported into the same library, // the catalog lent to C++ for a call, and a session's config filled in field by field. // // The local reads need no runtime: the waker counts wakes, and each poll that must wait @@ -156,8 +156,8 @@ int main() { const moq::frame_frame &hello = *frame->value(); CHECK(is(hello.payload(), "hello")); CHECK(!hello.timestamp()); - // When it arrived: moq-clock's `Instant`, from moq-clock's own export. - CHECK(moq::now().checked_duration_since(hello.arrived()).has_value()); + // When it arrived: kio's clock's `Instant`, from kio's own export. + CHECK(moq::clock_now().checked_duration_since(hello.arrived()).has_value()); } // A timestamp is a `Copy` value behind a handle: passing it copies it out. The track is @@ -216,7 +216,7 @@ int main() { // Paths own their segments, so C++ builds them, and relative ones, from a string. auto empty = moq::path::new_("/cam/"); - CHECK(!empty && empty.error() == moq::path_invalid::empty_segment); + CHECK(!empty && empty.error() == moq::path_error::empty_segment); auto cam = moq::path::new_("cam"); CHECK(cam && is(cam->as_str(), "cam")); auto joined = path.join(*cam); @@ -234,7 +234,7 @@ int main() { // made from the waker and held across polls, since what a poll registers lives only as // long as its waiter. It runs until every owner of the origin is gone. moq::waiter waiter = moq::waiter::new_(waker); - CHECK(driver.poll(moq::now(), waiter)); + CHECK(driver.poll(moq::clock_now(), waiter)); { // A client publishes the origin through moq-net's generic `with_publisher`, at each // type that implements its bound, by reference, so C++ keeps its handle. @@ -247,7 +247,7 @@ int main() { } // The last owner's drop wakes the driver, which then finishes, cleanly. CHECK(wakes > before); - auto closed = driver.poll(moq::now(), waiter); + auto closed = driver.poll(moq::clock_now(), waiter); CHECK(!closed && is(closed.error().to_string(), "closed")); // moq-mux reads an OpusHead into hang's catalog config: 44.1 kHz in, stereo, which the @@ -298,7 +298,9 @@ int main() { // A JSON track the catalog lists, published through moq-mux's generic // `json_snapshot` at a JSON document, and a section C++ parses from text, set in the // lent catalog and read back as text. - auto status = publisher.create_track("status", std::nullopt); + // Its track has a timescale, since every write is stamped. + auto milli = moq::timescale::milli(); + auto status = publisher.create_track("status", moq::track_info::default_().with_timescale(&milli)); CHECK(status); auto status_snapshot = catalog->json_snapshot_config(std::move(*status), moq::json_config::default_(), moq::json_options::default_()); @@ -306,7 +308,7 @@ int main() { CHECK(catalog->is_claimed_json_config("status")); // A value C++ parses is published as the track's latest, which a subscriber reads // back as JSON text. - CHECK(status_snapshot->update(moq::json::parse(R"({"on": true})").value())); + CHECK(status_snapshot->update(moq::json::parse(R"({"on": true})").value(), moq::timestamp::now())); moq::track_subscriber status_subscriber = status_snapshot->consume(); auto status_group = status_subscriber.recv_group().get(); CHECK(status_group && status_group.value()); @@ -424,7 +426,7 @@ int main() { auto established = std::move(*connection).established().get(); CHECK(established); - auto remote_broadcast = remote.request_broadcast("live", std::nullopt).get(); + auto remote_broadcast = remote.request("live", std::nullopt).get(); CHECK(remote_broadcast); auto remote_track = remote_broadcast->track("video"); CHECK(remote_track); diff --git a/ffi/moq/moq.dart b/ffi/moq/moq.dart index 0f214de8c..745067e15 100644 --- a/ffi/moq/moq.dart +++ b/ffi/moq/moq.dart @@ -1,6 +1,6 @@ // moq.c's script through the generated Dart: a broadcast's track, published and subscribed // to in one process, with every read awaited, and an origin, its driver run from the -// isolate's event loop, with moq-pattern, moq-clock, moq-tokio, hang, and moq-mux exported +// isolate's event loop, with moq-pattern, kio, moq-tokio, hang, and moq-mux exported // into the same library, and the catalog lent to Dart for a call. // // A local read that must wait completes once the write that wakes it lands, on the event @@ -54,9 +54,9 @@ Future drive(moq.OriginDriver driver) { // A wake posted before the driver finished may land after. if (finished.isCompleted) return; try { - final next = driver.poll(moq.now(), waiter); + final next = driver.poll(moq.clockNow(), waiter); if (next != null) { - deadline = Timer(next.saturatingDurationSince(moq.now()), poll); + deadline = Timer(next.saturatingDurationSince(moq.clockNow()), poll); next.close(); } } on moq.Error catch (e) { @@ -113,8 +113,8 @@ Future local() async { final hello = (await read.timeout(const Duration(seconds: 5)))!; expect(utf8.decode(hello.payload) == 'hello', 'a frame has its payload'); expect(hello.timestamp == null, 'an untimed frame has no timestamp'); - // When it arrived: moq-clock's `Instant`, from moq-clock's own export. - expect(moq.now().checkedDurationSince(hello.arrived()) != null, 'a frame arrived before now'); + // When it arrived: kio's clock's `Instant`, from kio's own export. + expect(moq.clockNow().checkedDurationSince(hello.arrived()) != null, 'a frame arrived before now'); // A timestamp is a `Copy` value behind a handle: passing it copies it out. The track is // untimed, so a timestamped frame is refused. @@ -167,9 +167,9 @@ Future origin() async { expect(path.asStr() == 'room/alice', 'a broadcast has its path'); // Paths own their segments, so Dart builds them, and relative ones, from a string. - throws( + throws( () => moq.Path('/cam/'), - (e) => e == moq.PathInvalid.emptySegment, + (e) => e == moq.PathError.emptySegment, 'an empty segment is its own error', ); final cam = moq.Path('cam'); @@ -236,7 +236,7 @@ Future catalog() async { // a JSON document, and a section Dart parses from text, set in the lent catalog and read // back as text. final status = producer.jsonSnapshotConfig( - publisher.createTrack('status', null), + publisher.createTrack('status', moq.TrackInfo.default_().withTimescale(moq.Timescale.milli)), moq.JsonConfig.default_(), moq.JsonOptions.default_(), ); @@ -245,7 +245,7 @@ Future catalog() async { // A value Dart parses is published as the track's latest, which a subscriber reads back // as JSON text. final on = moq.Json.parse('{"on": true}'); - status.update(on); + status.update(on, moq.Timestamp.now()); on.close(); final latest = await (await status.consume().recvGroup())?.readFrame(); expect(latest != null && utf8.decode(latest.payload) == '{"on":true}', 'a subscriber reads the latest value'); @@ -369,7 +369,7 @@ Future session() async { expect(connection != null, 'a client with a URL dials it'); final established = await connection!.established(); - final broadcast = await remote.requestBroadcast('live', null); + final broadcast = await remote.request('live', null); final subscriber = await broadcast.track('video').subscribe(null); final group = await subscriber.recvGroup(); final frame = await group?.readFrame(); diff --git a/ffi/moq/moq.go b/ffi/moq/moq.go index e6ca4bc3c..40bd7959f 100644 --- a/ffi/moq/moq.go +++ b/ffi/moq/moq.go @@ -1,6 +1,6 @@ // moq.c's script through the generated Go: a broadcast's track, published and subscribed // to in one process, with every read a blocking call taking a context, and an origin, with -// moq-pattern, moq-clock, moq-tokio, hang, and moq-mux exported into the same library, and +// moq-pattern, kio, moq-tokio, hang, and moq-mux exported into the same library, and // the catalog lent to Go for a call. // // The local reads need no runtime: each read that must wait blocks its goroutine until the @@ -99,14 +99,14 @@ func drive(driver *moq.OriginDriver) <-chan error { defer driver.Close() defer waiter.Close() for { - next, err := driver.Poll(moq.Now(), waiter) + next, err := driver.Poll(moq.ClockNow(), waiter) if err != nil { finished <- err return } var deadline <-chan time.Time if next != nil { - deadline = time.After(must(next.SaturatingDurationSince(moq.Now()))) + deadline = time.After(must(next.SaturatingDurationSince(moq.ClockNow()))) next.Close() } select { @@ -148,8 +148,8 @@ func script() { hello := within(frame, "a frame's arrival wakes the read") expect(string(must(hello.Payload()).Copy()) == "hello", "a frame has its payload") expect(must(hello.Timestamp()) == nil, "an untimed frame has no timestamp") - // When it arrived: moq-clock's `Instant`, from moq-clock's own export. - expect(must(moq.Now().CheckedDurationSince(must(hello.Arrived()))) != nil, "a frame arrived before now") + // When it arrived: kio's clock's `Instant`, from kio's own export. + expect(must(moq.ClockNow().CheckedDurationSince(must(hello.Arrived()))) != nil, "a frame arrived before now") // A timestamp is a `Copy` value behind a handle: passing it copies it out. The track is // untimed, so a timestamped frame is refused. @@ -200,8 +200,8 @@ func script() { // Paths own their segments, so Go builds them, and relative ones, from a string. _, err = moq.NewPath("/cam/") - var invalid moq.PathInvalid - expect(errors.As(err, &invalid) && invalid == moq.PathInvalidEmptySegment, "an empty segment is its own error") + var pathErr moq.PathError + expect(errors.As(err, &pathErr) && pathErr == moq.PathErrorEmptySegment, "an empty segment is its own error") cam := must(moq.NewPath("cam")) expect(must(cam.AsStr()) == "cam", "a path builds from its segments") joined := must(path.Join(cam)) @@ -266,13 +266,13 @@ func script() { // A JSON track the catalog lists, published through moq-mux's generic `json_snapshot` at // a JSON document, and a section Go parses from text, set in the lent catalog and read // back as text. - status := must(catalog.JsonSnapshotConfig(must(publisher.CreateTrack("status", nil)), moq.JsonConfigDefault(), moq.JsonOptionsDefault())) + status := must(catalog.JsonSnapshotConfig(must(publisher.CreateTrack("status", must(moq.TrackInfoDefault().WithTimescale(moq.TimescaleMILLI())))), moq.JsonConfigDefault(), moq.JsonOptionsDefault())) expect(must(status.Name()) == "status", "a JSON snapshot has its track's name") expect(must(catalog.IsClaimedJsonConfig("status")), "the JSON snapshot claims its name") // A value Go parses is published as the track's latest, which a subscriber reads back // as JSON text. on := must(moq.JsonParse(`{"on": true}`)) - expect(status.Update(on) == nil, "a JSON snapshot updates") + expect(status.Update(on, moq.TimestampNow()) == nil, "a JSON snapshot updates") on.Close() latest := must(must(must(status.Consume()).RecvGroup(ctx)).ReadFrame(ctx)) expect(latest != nil && string(must(latest.Payload()).Copy()) == `{"on":true}`, "a subscriber reads the latest value") @@ -397,7 +397,7 @@ func session() { expect(connection != nil, "a client with a URL dials it") established := must(connection.Established(ctx)) - subscriber := must(must(must(remote.RequestBroadcast(ctx, "live", nil)).Track("video")).Subscribe(ctx, nil)) + subscriber := must(must(must(remote.Request(ctx, "live", nil)).Track("video")).Subscribe(ctx, nil)) frame := must(must(subscriber.RecvGroup(ctx)).ReadFrame(ctx)) expect(frame != nil && string(must(frame.Payload()).Copy()) == "hello", "a session reads the frame the server serves") diff --git a/ffi/moq/moq.kt b/ffi/moq/moq.kt index 0f2276a37..d211e2a31 100644 --- a/ffi/moq/moq.kt +++ b/ffi/moq/moq.kt @@ -1,6 +1,6 @@ // moq.c's script through the generated Kotlin: a broadcast's track, published and // subscribed to in one process, with every read a suspend fun, and an origin, with -// moq-pattern, moq-clock, moq-tokio, hang, and moq-mux exported into the same library, +// moq-pattern, kio, moq-tokio, hang, and moq-mux exported into the same library, // and the catalog lent to Kotlin for a call. // // The local reads need no runtime: each read that must wait resumes on a dispatcher the @@ -86,8 +86,8 @@ fun main() { val hello = frame!! expect(hello.payload.toByteArray().decodeToString() == "hello", "a frame has its payload") expect(hello.timestamp == null, "an untimed frame has no timestamp") - // When it arrived: moq-clock's `Instant`, from moq-clock's own export. - expect(now().checkedDurationSince(hello.arrived()) != null, "a frame arrived before now") + // When it arrived: kio's clock's `Instant`, from kio's own export. + expect(clockNow().checkedDurationSince(hello.arrived()) != null, "a frame arrived before now") // A timestamp is a `Copy` value behind a handle: passing it copies it out. The track is // untimed, so a timestamped frame is refused. @@ -140,8 +140,8 @@ fun main() { try { Path.new("/cam/") expect(false, "a path with an empty segment is an error") - } catch (e: PathInvalidException) { - expect(e.error == PathInvalid.EMPTY_SEGMENT, "an empty segment is its own error") + } catch (e: PathErrorException) { + expect(e.error == PathError.EMPTY_SEGMENT, "an empty segment is its own error") } val cam = Path.new("cam") expect(cam.asStr() == "cam", "a path builds from its segments") @@ -157,7 +157,7 @@ fun main() { // long as its waiter. It runs until every owner of the origin is gone. var wakes = 0 val waiter = Waiter.new(Waker { wakes++ }) - driver.poll(now(), waiter) + driver.poll(clockNow(), waiter) // A client publishes the origin through moq-net's generic `with_publisher`, at each // type that implements its bound, by reference, so Kotlin keeps its handle. Client.new().withPublisherProducer(origin).close() @@ -166,7 +166,7 @@ fun main() { // The last owner's close wakes the driver, which then finishes, cleanly. expect(wakes > before, "the last owner's close wakes the driver") try { - driver.poll(now(), waiter) + driver.poll(clockNow(), waiter) expect(false, "a driver whose origin is gone finishes") } catch (e: ErrorException) { expect(e.message == "closed", "a finished driver's error is Rust's") @@ -211,12 +211,12 @@ fun main() { // A JSON track the catalog lists, published through moq-mux's generic `json_snapshot` at // a JSON document, and a section Kotlin parses from text, set in the lent catalog and // read back as text. - val status = catalog.jsonSnapshotConfig(publisher.createTrack("status", null), JsonConfig.default(), JsonOptions.default()) + val status = catalog.jsonSnapshotConfig(publisher.createTrack("status", TrackInfo.default().withTimescale(Timescale.MILLI)), JsonConfig.default(), JsonOptions.default()) expect(status.name() == "status", "a JSON snapshot has its track's name") expect(catalog.isClaimedJsonConfig("status"), "the JSON snapshot claims its name") // A value Kotlin parses is published as the track's latest, which a subscriber reads // back as JSON text. - Json.parse("""{"on": true}""").use { status.update(it) } + Json.parse("""{"on": true}""").use { status.update(it, Timestamp.now()) } runBlocking { val frame = status.consume().recvGroup()?.readFrame() expect(frame?.payload?.toByteArray()?.decodeToString() == """{"on":true}""", "a subscriber reads the latest value") @@ -340,7 +340,7 @@ fun main() { expect(connection != null, "a client with a URL dials it") val established = connection!!.established() - val subscriber = remote.requestBroadcast("live", null).track("video").subscribe(null) + val subscriber = remote.request("live", null).track("video").subscribe(null) val frame = subscriber.recvGroup()?.readFrame() expect(frame?.payload?.toByteArray()?.decodeToString() == "hello", "a session reads the frame the server serves") diff --git a/ffi/moq/moq.py b/ffi/moq/moq.py index d55fce946..c561b5872 100644 --- a/ffi/moq/moq.py +++ b/ffi/moq/moq.py @@ -1,6 +1,6 @@ # moq.c's script through the generated Python: a broadcast's track, published and # subscribed to in one process, with every read awaited, and an origin, its driver run on -# asyncio's loop, with moq-pattern, moq-clock, moq-tokio, hang, and moq-mux exported into +# asyncio's loop, with moq-pattern, kio, moq-tokio, hang, and moq-mux exported into # the same library, and the catalog lent to Python for a call. # # A local read that must wait resumes once the write that wakes it lands, on the loop the @@ -41,8 +41,8 @@ async def drive(driver): waiter = moq.Waiter.new(moq.Waker(woken.set)) while True: woken.clear() - deadline = driver.poll(moq.now(), waiter) - timeout = None if deadline is None else deadline.saturating_duration_since(moq.now()).total_seconds() + deadline = driver.poll(moq.clock_now(), waiter) + timeout = None if deadline is None else deadline.saturating_duration_since(moq.clock_now()).total_seconds() try: await asyncio.wait_for(woken.wait(), timeout) except TimeoutError: @@ -80,8 +80,8 @@ def local(loop): hello = loop.run_until_complete(asyncio.wait_for(read, 5)) expect(text(hello.payload) == "hello", "a frame has its payload") expect(hello.timestamp is None, "an untimed frame has no timestamp") - # When it arrived: moq-clock's `Instant`, from moq-clock's own export. - expect(moq.now().checked_duration_since(hello.arrived()) is not None, "a frame arrived before now") + # When it arrived: kio's clock's `Instant`, from kio's own export. + expect(moq.clock_now().checked_duration_since(hello.arrived()) is not None, "a frame arrived before now") # A timestamp is a `Copy` value behind a handle: passing it copies it out. The track is # untimed, so a timestamped frame is refused. @@ -130,8 +130,8 @@ def origin(loop): try: moq.Path.new("/cam/") expect(False, "a path with an empty segment is an error") - except moq.PathInvalidException as e: - expect(e.error == moq.PathInvalid.EMPTY_SEGMENT, "an empty segment is its own error") + except moq.PathErrorException as e: + expect(e.error == moq.PathError.EMPTY_SEGMENT, "an empty segment is its own error") cam = moq.Path.new("cam") expect(cam.as_str() == "cam", "a path builds from its segments") joined = path.join(cam) @@ -203,13 +203,13 @@ def count(lent): # a JSON document, and a section Python parses from text, set in the lent catalog and # read back as text. status = producer.json_snapshot_config( - publisher.create_track("status", None), moq.JsonConfig.default(), moq.JsonOptions.default() + publisher.create_track("status", moq.TrackInfo.default().with_timescale(moq.Timescale.MILLI)), moq.JsonConfig.default(), moq.JsonOptions.default() ) expect(status.name() == "status", "a JSON snapshot has its track's name") expect(producer.is_claimed_json_config("status"), "the JSON snapshot claims its name") # A value Python parses is published as the track's latest, which a subscriber reads # back as JSON text. - status.update(moq.Json.parse('{"on": true}')) + status.update(moq.Json.parse('{"on": true}'), moq.Timestamp.now()) async def latest(): group = await status.consume().recv_group() @@ -329,7 +329,7 @@ async def session(): expect(connection is not None, "a client with a URL dials it") established = await connection.established() - broadcast = await remote.request_broadcast("live", None) + broadcast = await remote.request("live", None) subscriber = await broadcast.track("video").subscribe(None) group = await subscriber.recv_group() frame = None if group is None else await group.read_frame() diff --git a/ffi/moq/moq.swift b/ffi/moq/moq.swift index e3ab62423..06a787a33 100644 --- a/ffi/moq/moq.swift +++ b/ffi/moq/moq.swift @@ -1,6 +1,6 @@ // moq.c's script through the generated Swift: a broadcast's track, published and // subscribed to in one process, with every read an async func, and an origin, with -// moq-pattern, moq-clock, moq-tokio, hang, and moq-mux exported into the same library, +// moq-pattern, kio, moq-tokio, hang, and moq-mux exported into the same library, // and the catalog lent to Swift for a call. // // The local reads need no runtime: each read that must wait resumes on an executor the @@ -124,7 +124,7 @@ func origin(_ waiter: Waiter, _ wakes: Cell) throws -> (OriginDriver, Int) do { _ = try Path.new("/cam/") expect(false, "a path with an empty segment is an error") - } catch let e as PathInvalid { + } catch let e as PathError { expect(e == .emptySegment, "an empty segment is its own error") } let cam = try Path.new("cam") @@ -136,7 +136,7 @@ func origin(_ waiter: Waiter, _ wakes: Cell) throws -> (OriginDriver, Int) expect(try joined.resolve(Relative.new("../bob")).asStr() == "room/bob", "a relative path resolves") } - _ = try driver.poll(now(), waiter) + _ = try driver.poll(clockNow(), waiter) // A client publishes the origin through moq-net's generic `with_publisher`, at each // type that implements its bound, by reference, so Swift keeps its handle. _ = Client.new().withPublisherProducer(origin) @@ -182,8 +182,8 @@ func script() throws { let hello = frame.value! expect(text(hello.payload) == "hello", "a frame has its payload") expect(hello.timestamp == nil, "an untimed frame has no timestamp") - // When it arrived: moq-clock's `Instant`, from moq-clock's own export. - expect(now().checkedDurationSince(hello.arrived()) != nil, "a frame arrived before now") + // When it arrived: kio's clock's `Instant`, from kio's own export. + expect(clockNow().checkedDurationSince(hello.arrived()) != nil, "a frame arrived before now") // A timestamp is a `Copy` value behind a handle: passing it copies it out. The track is // untimed, so a timestamped frame is refused. @@ -215,7 +215,7 @@ func script() throws { let (driver, before) = try origin(waiter, wakes) expect(wakes.value > before, "the last owner's release wakes the driver") do { - _ = try driver.poll(now(), waiter) + _ = try driver.poll(clockNow(), waiter) expect(false, "a driver whose origin is gone finishes") } catch let e as Moq.Error { expect("\(e)" == "closed", "a finished driver's error is Rust's") @@ -263,13 +263,13 @@ func script() throws { // a JSON document, and a section Swift parses from text, set in the lent catalog and // read back as text. let status = try catalog.jsonSnapshotConfig( - try publisher.createTrack("status", nil), JsonConfig.default(), JsonOptions.default()) + try publisher.createTrack("status", TrackInfo.default().withTimescale(Timescale.MILLI)), JsonConfig.default(), JsonOptions.default()) expect(status.name() == "status", "a JSON snapshot has its track's name") expect(catalog.isClaimedJsonConfig("status"), "the JSON snapshot claims its name") // A value Swift parses is published as the track's latest, which a subscriber reads // back as JSON text. let on = try Json.parse(#"{"on": true}"#) - try status.update(on) + try status.update(on, Timestamp.now()) try block { let frame = try await status.consume().recvGroup()?.readFrame() expect(text(frame?.payload) == #"{"on":true}"#, "a subscriber reads the latest value") @@ -385,7 +385,7 @@ func script() throws { expect(connection != nil, "a client with a URL dials it") let established = try await connection!.established() - let subscriber = try await remote.requestBroadcast("live", nil).track("video").subscribe(nil) + let subscriber = try await remote.request("live", nil).track("video").subscribe(nil) let frame = try await subscriber.recvGroup()?.readFrame() expect(text(frame?.payload) == "hello", "a session reads the frame the server serves") diff --git a/quest/m1/ffi-export/README.md b/quest/m1/ffi-export/README.md index 0ad16fab3..6afb3de85 100644 --- a/quest/m1/ffi-export/README.md +++ b/quest/m1/ffi-export/README.md @@ -61,10 +61,8 @@ names and defaults, and fails the export on a public item it cannot export. ## Required -- [moq-clock's C names once it moves into kio](/quest/m1/ffi-export/crates.md) - `moq_now` and `moq_origin` become the clock's, after moq moves it into `kio::clock` - [C names and crates with no config](/quest/m1/ffi-export/names.md) - a collision-free naming rule, a facade crate, and the prefix from Cargo, leaving only `exclude` in `ffi.toml` - [Sessions on sans-IO QUIC](/quest/m1/ffi-export/session-quic.md) - bindings move UDP datagrams and drive the session's poll themselves, over moq's runtime-free QUIC driver once it lands - [C++ bindings on MSVC](/quest/m1/ffi-export/cpp.md) - the nightly's MSVC job passes the C++ checks - [Generated Python type-checks](/quest/m1/ffi-export/python-types.md) - mypy and pyright pass on the generated Python - [Every public item exports](/quest/m1/ffi-export/refusals.md) - the census is empty and `ffi.toml` is deleted, so the exporter reads no config -- [Waiting: moq-tokio within the recursion limit](/quest/m1/ffi-export/recursion-limit.md) - the exporter's moq pin moves past moq-dev/moq#7032 once moq-tokio's `Send` check fits the limit diff --git a/quest/m1/ffi-export/crates.md b/quest/m1/ffi-export/crates.md deleted file mode 100644 index 13e032f04..000000000 --- a/quest/m1/ffi-export/crates.md +++ /dev/null @@ -1,17 +0,0 @@ -# [S] moq-clock's C names once it moves into kio - -## Goal - -moq-clock's items export with C names that say they are the clock's: today `now()` and -`origin()` are `moq_now` and `moq_origin`, which reads like moq-net's origin types. - -This waits on moq's [kio::clock quest](https://github.com/moq-dev/moq/blob/main/quest/m1/kio-clock.md), -which moves moq-clock into `kio::clock`. It has landed once `rs/moq-clock` is gone from -moq's `main`. - -## Plan - -Then bump the moq pin and re-export `kio::clock` in moq-clock's place in `just ffi -standin`, so the naming rule names its items `moq_clock_*`. - -Public API: the C names moq's export emits. diff --git a/quest/m1/ffi-export/recursion-limit.md b/quest/m1/ffi-export/recursion-limit.md deleted file mode 100644 index 4b894644a..000000000 --- a/quest/m1/ffi-export/recursion-limit.md +++ /dev/null @@ -1,19 +0,0 @@ -# [S] Waiting: moq-tokio within the recursion limit - -## Goal - -`just ffi moq` exports moq's `main` again. [moq-dev/moq#7032](https://github.com/moq-dev/moq/pull/7032) -made moq-tokio's `tokio::spawn` of `moq_net::time::run` overflow the trait solver's -recursion limit proving `Send`, a future-compat lint on the exporter's nightly that the -recipe denies so a later nightly cannot break the export. Until moq fixes it, the -exporter's moq pin cannot move past it and misses moq's later API. - -Check: on the exporter's nightly, in a moq checkout, -`RUSTFLAGS="-D recursion_depth_exceeding_limit" cargo check -p moq-tokio`. Advance it with a -quest in moq. - -## Plan - -Once it passes on moq's `main`, drop the exporter's own `moq_rev` for `just ts moq`'s, so the -translator and the exporter share one moq pin again, and update the config to what -`just ffi census` then refuses. diff --git a/ts/bench/moq.ts b/ts/bench/moq.ts index fdea42eb3..a30f39eda 100644 --- a/ts/bench/moq.ts +++ b/ts/bench/moq.ts @@ -202,7 +202,7 @@ function decodeSplit(data: Uint8Array, version: Version): Uint8Array { } function encodeGroup(data: Uint8Array, version: Version): Promise { - const group = new Group({ subscribe: u64(data, 0), sequence: Number(u64(data, 1)) }); + const group = new Group({ subscribe: u64(data, 0), sequence: u64(data, 1) }); return Writer.collect(version, (w) => group.encode(w, version)); } diff --git a/ts/fixtures/model/Cargo.lock b/ts/fixtures/model/Cargo.lock index d6b9f26cf..61fe98d7e 100644 --- a/ts/fixtures/model/Cargo.lock +++ b/ts/fixtures/model/Cargo.lock @@ -8,12 +8,6 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - [[package]] name = "bytes" version = "1.12.1" @@ -112,30 +106,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "slab", -] - [[package]] name = "getrandom" version = "0.4.3" @@ -280,31 +250,21 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "js-sys" -version = "0.3.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - [[package]] name = "kio" version = "0.6.2" -source = "git+https://github.com/moq-dev/moq?rev=4b8e37bd44e407b3416a17992720870ea3f9221c#4b8e37bd44e407b3416a17992720870ea3f9221c" +source = "git+https://github.com/moq-dev/moq?rev=ffcb7cd9748422daa30e935ec464108b5a8ebb71#ffcb7cd9748422daa30e935ec464108b5a8ebb71" dependencies = [ "crossbeam-epoch", "kio-macros", + "rand", "smallvec", ] [[package]] name = "kio-macros" version = "0.1.0" -source = "git+https://github.com/moq-dev/moq?rev=4b8e37bd44e407b3416a17992720870ea3f9221c#4b8e37bd44e407b3416a17992720870ea3f9221c" +source = "git+https://github.com/moq-dev/moq?rev=ffcb7cd9748422daa30e935ec464108b5a8ebb71#ffcb7cd9748422daa30e935ec464108b5a8ebb71" dependencies = [ "proc-macro2", "quote", @@ -334,13 +294,12 @@ name = "model-fixture" version = "0.0.0" dependencies = [ "moq-net", - "uuid", ] [[package]] name = "moq-net" version = "0.3.10" -source = "git+https://github.com/moq-dev/moq?rev=4b8e37bd44e407b3416a17992720870ea3f9221c#4b8e37bd44e407b3416a17992720870ea3f9221c" +source = "git+https://github.com/moq-dev/moq?rev=ffcb7cd9748422daa30e935ec464108b5a8ebb71#ffcb7cd9748422daa30e935ec464108b5a8ebb71" dependencies = [ "arrayvec", "bytes", @@ -357,13 +316,12 @@ dependencies = [ "thiserror", "tracing", "url", - "uuid", ] [[package]] name = "moq-pattern" version = "0.1.2" -source = "git+https://github.com/moq-dev/moq?rev=4b8e37bd44e407b3416a17992720870ea3f9221c#4b8e37bd44e407b3416a17992720870ea3f9221c" +source = "git+https://github.com/moq-dev/moq?rev=ffcb7cd9748422daa30e935ec464108b5a8ebb71#ffcb7cd9748422daa30e935ec464108b5a8ebb71" dependencies = [ "serde", ] @@ -675,72 +633,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "uuid" -version = "1.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" -dependencies = [ - "getrandom", - "js-sys", - "uuid-rng-internal", - "wasm-bindgen", -] - -[[package]] -name = "uuid-rng-internal" -version = "1.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf60f3d36825d0911af7e102da39cc817280560cad48e81df1eb5c4d7400f76" -dependencies = [ - "getrandom", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.6", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" -dependencies = [ - "unicode-ident", -] - [[package]] name = "winnow" version = "1.0.4" diff --git a/ts/fixtures/model/Cargo.toml b/ts/fixtures/model/Cargo.toml index 70e95f788..25d64251e 100644 --- a/ts/fixtures/model/Cargo.toml +++ b/ts/fixtures/model/Cargo.toml @@ -6,9 +6,6 @@ edition = "2024" license = "MIT OR Apache-2.0" publish = false -[package.metadata.cargo-shear] -ignored = ["uuid"] - # Its own workspace and lockfile, so the moq checkout it builds stays out of the # rust2-ts package's inputs. [workspace] @@ -18,14 +15,9 @@ ignored = ["uuid"] crate-type = ["lib", "cdylib"] [dependencies] -# The moq pin, which `just ts moq` and `just ts census` check out too; bumping it is +# The moq pin, which `just ts moq`, `just ts census`, and `just ffi` check out too; bumping it is # `cargo update -p moq-net` after this rev. Without `async`, the model builds without a runtime. -moq-net = { git = "https://github.com/moq-dev/moq", rev = "4b8e37bd44e407b3416a17992720870ea3f9221c", default-features = false } - -# uuid, which moq-net names, refuses to build for wasm32 without a source of randomness. -# The harness reaches none, so the native side picks getrandom's custom backend, which it never calls. -[target.'cfg(target_arch = "wasm32")'.dependencies] -uuid = { version = "1.29.0", features = ["rng-getrandom"] } +moq-net = { git = "https://github.com/moq-dev/moq", rev = "ffcb7cd9748422daa30e935ec464108b5a8ebb71", default-features = false } # The ts workspace's differential profile, which the tests build the native side with. [profile.differential] diff --git a/ts/fixtures/model/expected/harness.ts b/ts/fixtures/model/expected/harness.ts index 55a953d95..abbba7835 100644 --- a/ts/fixtures/model/expected/harness.ts +++ b/ts/fixtures/model/expected/harness.ts @@ -4,7 +4,7 @@ import * as rs from "./rs.ts"; import { from$convert_From_moq_net_Timescale_u64 as time_from$convert_From_moq_net_Timescale_u64, from$convert_From_moq_net_Timescale_num_NonZero_u64 as time_from$convert_From_moq_net_Timescale_num_NonZero_u64, from$convert_From_moq_net_Timestamp_time_Duration as time_from$convert_From_moq_net_Timestamp_time_Duration } from "./moq_net/model/time.ts"; import * as TimeOverflow from "./moq_net/model/time.TimeOverflow.ts"; import * as Timescale from "./moq_net/model/time.Timescale.ts"; -import * as time_Timestamp from "./moq_net/model/time.Timestamp.ts"; +import * as Timestamp from "./moq_net/model/time.Timestamp.ts"; /** * Each case's name and what it runs. The first byte picks one modulo their count, so a @@ -72,11 +72,11 @@ export function takeScale(data: rs.ByteSlice): [rs.u64, rs.ByteSlice] { } /** A timestamp, or the error its value or scale makes. */ -export function takeTimestamp(data: rs.ByteSlice): [rs.Result, rs.ByteSlice] { +export function takeTimestamp(data: rs.ByteSlice): [rs.Result, rs.ByteSlice] { const _4: [rs.u64, rs.ByteSlice] = takeValue(data); const value: rs.u64 = _4[0]; const _7: [rs.u64, rs.ByteSlice] = takeScale(_4[1]); - return [time_Timestamp.fromScale(value, _7[0]), _7[1]] as [rs.Result, rs.ByteSlice]; + return [Timestamp.fromScale(value, _7[0]), _7[1]] as [rs.Result, rs.ByteSlice]; } export function putU64(out: rs.ByteVec, value: rs.u64): void { @@ -162,9 +162,9 @@ export function putResult$moq_net_Timescale_harness_timescales_closure2(out: rs. } /** A result's tag, then the value `ok` records or the error's `Display` and `Debug`. */ -export function putResult$moq_net_Timestamp_harness_durations_closure0(out: rs.ByteVec, result: rs.Result, ok: (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined): void { +export function putResult$moq_net_Timestamp_harness_durations_closure0(out: rs.ByteVec, result: rs.Result, ok: (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined): void { if (result.ok) { - const value: time_Timestamp.Timestamp = result.value; + const value: Timestamp.Timestamp = result.value; rs.byteVecPush(out, 1); ok(out, value); } else { @@ -178,9 +178,9 @@ export function putResult$moq_net_Timestamp_harness_durations_closure0(out: rs.B } /** A result's tag, then the value `ok` records or the error's `Display` and `Debug`. */ -export function putResult$moq_net_Timestamp_harness_put_timestamp(out: rs.ByteVec, result: rs.Result, _: (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined): void { +export function putResult$moq_net_Timestamp_harness_put_timestamp(out: rs.ByteVec, result: rs.Result, _: (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined): void { if (result.ok) { - const value: time_Timestamp.Timestamp = result.value; + const value: Timestamp.Timestamp = result.value; rs.byteVecPush(out, 1); putTimestamp(out, value); } else { @@ -194,15 +194,15 @@ export function putResult$moq_net_Timestamp_harness_put_timestamp(out: rs.ByteVe } /** Everything a timestamp reads back. */ -export function putTimestamp(out: rs.ByteVec, time: time_Timestamp.Timestamp): void { - putU64(out, time_Timestamp.value(time)); - putU64(out, Timescale.asU64(time_Timestamp.scale(time))); - rs.byteVecPush(out, time_Timestamp.isZero(time) ? 1 : 0); - putU64(out, time_Timestamp.asSecs(time)); - putU128(out, time_Timestamp.asMillis(time)); - putU128(out, time_Timestamp.asMicros(time)); - putU128(out, time_Timestamp.asNanos(time)); - const args2: rs.Fmt[] = [(f: rs.Formatter) => time_Timestamp.fmt$fmt_Debug(time, f)]; +export function putTimestamp(out: rs.ByteVec, time: Timestamp.Timestamp): void { + putU64(out, Timestamp.value(time)); + putU64(out, Timescale.asU64(Timestamp.scale(time))); + rs.byteVecPush(out, Timestamp.isZero(time) ? 1 : 0); + putU64(out, Timestamp.asSecs(time)); + putU128(out, Timestamp.asMillis(time)); + putU128(out, Timestamp.asMicros(time)); + putU128(out, Timestamp.asNanos(time)); + const args2: rs.Fmt[] = [(f: rs.Formatter) => Timestamp.fmt$fmt_Debug(time, f)]; putText(out, rs.fmtFormat([args2[0]])); } @@ -233,26 +233,26 @@ export function timescales(data: rs.ByteSlice, out: rs.ByteVec): void { export function timestamps(data: rs.ByteSlice, out: rs.ByteVec): void { const _5: [rs.u64, rs.ByteSlice] = takeValue(data); const value: rs.u64 = _5[0]; - putResult$moq_net_Timestamp_harness_put_timestamp(out, time_Timestamp.fromScale(value, takeScale(_5[1])[0]), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); - putResult$moq_net_Timestamp_harness_put_timestamp(out, time_Timestamp.fromSecs(value), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); - putResult$moq_net_Timestamp_harness_put_timestamp(out, time_Timestamp.fromMillis(value), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); - putResult$moq_net_Timestamp_harness_put_timestamp(out, time_Timestamp.fromMicros(value), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); - putResult$moq_net_Timestamp_harness_put_timestamp(out, time_Timestamp.fromNanos(value), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); + putResult$moq_net_Timestamp_harness_put_timestamp(out, Timestamp.fromScale(value, takeScale(_5[1])[0]), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); + putResult$moq_net_Timestamp_harness_put_timestamp(out, Timestamp.fromSecs(value), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); + putResult$moq_net_Timestamp_harness_put_timestamp(out, Timestamp.fromMillis(value), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); + putResult$moq_net_Timestamp_harness_put_timestamp(out, Timestamp.fromMicros(value), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); + putResult$moq_net_Timestamp_harness_put_timestamp(out, Timestamp.fromNanos(value), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); putTimestamp(out, { value: 0, scale: { _0: 1 } }); } /** A timestamp re-expressed at another scale. */ export function conversions(data: rs.ByteSlice, out: rs.ByteVec): void { - const _5: [rs.Result, rs.ByteSlice] = takeTimestamp(data); - const time: rs.Result = _5[0]; + const _5: [rs.Result, rs.ByteSlice] = takeTimestamp(data); + const time: rs.Result = _5[0]; const target: rs.u64 = takeScale(_5[1])[0]; if (time.ok) { - const time2: time_Timestamp.Timestamp = time.value; + const time2: Timestamp.Timestamp = time.value; putResult$moq_net_Timescale_harness_conversions_closure0(out, Timescale.new(target), (out2: rs.ByteVec, target2: Timescale.Timescale): undefined => { - putU128(out2, time_Timestamp.asScale(time2, target2)); - putResult$moq_net_Timestamp_harness_put_timestamp(out2, time_Timestamp.convert(time2, target2), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); + putU128(out2, Timestamp.asScale(time2, target2)); + putResult$moq_net_Timestamp_harness_put_timestamp(out2, Timestamp.convert(time2, target2), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); }); - putResult$moq_net_Timestamp_harness_put_timestamp(out, time_Timestamp.convert(time2, { _0: 1000 }), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); + putResult$moq_net_Timestamp_harness_put_timestamp(out, Timestamp.convert(time2, { _0: 1000 }), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); } else { rs.byteVecPush(out, 238); } @@ -260,28 +260,28 @@ export function conversions(data: rs.ByteSlice, out: rs.ByteVec): void { /** Two timestamps added, subtracted, compared, and sorted with a third. */ export function arithmetic(data: rs.ByteSlice, out: rs.ByteVec): void { - const _5: [rs.Result, rs.ByteSlice] = takeTimestamp(data); - const a: rs.Result = _5[0]; - const _8: [rs.Result, rs.ByteSlice] = takeTimestamp(_5[1]); + const _5: [rs.Result, rs.ByteSlice] = takeTimestamp(data); + const a: rs.Result = _5[0]; + const _8: [rs.Result, rs.ByteSlice] = takeTimestamp(_5[1]); b0: { - const b: rs.Result = _8[0]; - const c: rs.Result = takeTimestamp(_8[1])[0]; + const b: rs.Result = _8[0]; + const c: rs.Result = takeTimestamp(_8[1])[0]; if (a.ok) { if (b.ok) { - const a2: time_Timestamp.Timestamp = a.value; - const b2: time_Timestamp.Timestamp = b.value; - putResult$moq_net_Timestamp_harness_put_timestamp(out, time_Timestamp.checkedAdd(a2, b2), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); - putResult$moq_net_Timestamp_harness_put_timestamp(out, time_Timestamp.checkedSub(a2, b2), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); - putResult$moq_net_Timestamp_harness_put_timestamp(out, time_Timestamp.checkedSub(b2, a2), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); - putOrdering(out, time_Timestamp.cmp$cmp_Ord(a2, b2)); - rs.byteVecPush(out, time_Timestamp.eq$cmp_PartialEq(a2, b2) ? 1 : 0); - rs.byteVecPush(out, time_Timestamp.partialCmp$cmp_PartialOrd(a2, b2) === -1 ? 1 : 0); - rs.byteVecPush(out, time_Timestamp.partialCmp$cmp_PartialOrd(a2, b2) === 1 ? 1 : 0); - const part = rs.ordMax(a2, b2, rs.cmpLt(time_Timestamp.partialCmp$cmp_PartialOrd)); - const args2: rs.Fmt[] = [(f: rs.Formatter) => time_Timestamp.fmt$fmt_Debug(part, f)]; + const a2: Timestamp.Timestamp = a.value; + const b2: Timestamp.Timestamp = b.value; + putResult$moq_net_Timestamp_harness_put_timestamp(out, Timestamp.checkedAdd(a2, b2), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); + putResult$moq_net_Timestamp_harness_put_timestamp(out, Timestamp.checkedSub(a2, b2), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); + putResult$moq_net_Timestamp_harness_put_timestamp(out, Timestamp.checkedSub(b2, a2), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); + putOrdering(out, Timestamp.cmp$cmp_Ord(a2, b2)); + rs.byteVecPush(out, Timestamp.eq$cmp_PartialEq(a2, b2) ? 1 : 0); + rs.byteVecPush(out, Timestamp.partialCmp$cmp_PartialOrd(a2, b2) === -1 ? 1 : 0); + rs.byteVecPush(out, Timestamp.partialCmp$cmp_PartialOrd(a2, b2) === 1 ? 1 : 0); + const part = rs.ordMax(a2, b2, rs.cmpLt(Timestamp.partialCmp$cmp_PartialOrd)); + const args2: rs.Fmt[] = [(f: rs.Formatter) => Timestamp.fmt$fmt_Debug(part, f)]; putText(out, rs.fmtFormat([args2[0]])); - const sorted: rs.Vec = rs.iterToVec(rs.iterChain(rs.iterSlice([a2, b2]), rs.iterResult(c)), undefined, 16, true); - rs.sliceSort(rs.sliceFrom(sorted.items), rs.cmpLt(time_Timestamp.partialCmp$cmp_PartialOrd), 16, true); + const sorted: rs.Vec = rs.iterToVec(rs.iterChain(rs.iterSlice([a2, b2]), rs.iterResult(c)), undefined, 16, true); + rs.sliceSort(rs.sliceFrom(sorted.items), rs.cmpLt(Timestamp.partialCmp$cmp_PartialOrd), 16, true); for (const _65 of sorted.items) { putTimestamp(out, _65); } @@ -297,8 +297,8 @@ export function durations(data: rs.ByteSlice, out: rs.ByteVec): void { const _5: [rs.u64, rs.ByteSlice] = takeValue(data); const secs: rs.u64 = _5[0]; const _8: [rs.u64, rs.ByteSlice] = takeU64(_5[1]); - putResult$moq_net_Timestamp_harness_put_timestamp(out, time_Timestamp.tryFrom$convert_TryFrom_time_Duration(rs.durationNew(secs, rs.u64Low(rs.u64Rem(_8[0], 1000000000)))), putTimestamp as (a0: rs.ByteVec, a1: time_Timestamp.Timestamp) => undefined); - putResult$moq_net_Timestamp_harness_durations_closure0(out, takeTimestamp(_8[1])[0], (out2: rs.ByteVec, time2: time_Timestamp.Timestamp): undefined => { + putResult$moq_net_Timestamp_harness_put_timestamp(out, Timestamp.tryFrom$convert_TryFrom_time_Duration(rs.durationNew(secs, rs.u64Low(rs.u64Rem(_8[0], 1000000000)))), putTimestamp as (a0: rs.ByteVec, a1: Timestamp.Timestamp) => undefined); + putResult$moq_net_Timestamp_harness_durations_closure0(out, takeTimestamp(_8[1])[0], (out2: rs.ByteVec, time2: Timestamp.Timestamp): undefined => { const duration2: rs.Duration = time_from$convert_From_moq_net_Timestamp_time_Duration(time2); putU64(out2, rs.durationAsSecs(duration2)); rs.byteVecExtendFromSlice(out2, rs.byteSliceFrom(rs.u32ToBeBytes(rs.durationSubsecNanos(duration2)))); diff --git a/ts/fixtures/model/expected/moq_net/model/time.ts b/ts/fixtures/model/expected/moq_net/model/time.ts index b08844d7a..e63566c47 100644 --- a/ts/fixtures/model/expected/moq_net/model/time.ts +++ b/ts/fixtures/model/expected/moq_net/model/time.ts @@ -2,7 +2,7 @@ import * as rs from "../../rs.ts"; import type * as Timescale from "../../moq_net/model/time.Timescale.ts"; -import * as time_Timestamp from "../../moq_net/model/time.Timestamp.ts"; +import * as Timestamp from "../../moq_net/model/time.Timestamp.ts"; export function bounded(value: rs.u128, max: rs.u64): rs.u64 | undefined { if (!rs.u128Le(value, rs.u128Cast(max))) { @@ -19,9 +19,9 @@ export function from$convert_From_moq_net_Timescale_num_NonZero_u64(scale: Times return scale._0; } -export function from$convert_From_moq_net_Timestamp_time_Duration(time: time_Timestamp.Timestamp): rs.Duration { - const nanos: rs.u128 = time_Timestamp.asNanos(time); - return rs.durationNew(time_Timestamp.asSecs(time), rs.u128Low(rs.u128Rem(nanos, 1000000000))); +export function from$convert_From_moq_net_Timestamp_time_Duration(time: Timestamp.Timestamp): rs.Duration { + const nanos: rs.u128 = Timestamp.asNanos(time); + return rs.durationNew(Timestamp.asSecs(time), rs.u128Low(rs.u128Rem(nanos, 1000000000))); } //# sourceMappingURL=time.ts.map diff --git a/ts/justfile b/ts/justfile index dbb43ffac..b8bd3c34e 100644 --- a/ts/justfile +++ b/ts/justfile @@ -11,7 +11,8 @@ export PATH := if env("RUST2_TS_TOOLCHAIN", "") == "" { env("PATH") } else { env # The model fixture, its own workspace, which builds moq-net at the moq pin. model := "fixtures/model/Cargo.toml" -# The moq revision `moq` and `census` translate by default: the one the model fixture builds. +# The moq revision `moq` and `census` translate and `just ffi` exports by default: the one +# the model fixture builds. moq_rev := `taplo get -f fixtures/model/Cargo.toml dependencies.moq-net.rev` default: