initial macos porting work
This commit is contained in:
20
Cargo.lock
generated
20
Cargo.lock
generated
@@ -1308,7 +1308,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
@@ -1325,6 +1327,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
@@ -1822,6 +1826,7 @@ dependencies = [
|
||||
"ruin-runtime",
|
||||
"ruin_reactivity",
|
||||
"ruin_ui",
|
||||
"ruin_ui_platform_macos",
|
||||
"ruin_ui_platform_wayland",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
@@ -1850,6 +1855,21 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruin_ui_platform_macos"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"raw-window-handle",
|
||||
"ruin-runtime",
|
||||
"ruin_ui",
|
||||
"ruin_ui_renderer_wgpu",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruin_ui_platform_wayland"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -8,9 +8,14 @@ ruin_reactivity = { path = "../reactivity" }
|
||||
ruin_runtime = { package = "ruin-runtime", path = "../runtime" }
|
||||
ruin_app_proc_macros = { package = "ruin-app-proc-macros", path = "../ruin_app_proc_macros" }
|
||||
ruin_ui = { path = "../ui" }
|
||||
ruin_ui_platform_wayland = { path = "../ui_platform_wayland" }
|
||||
tracing = "0.1"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
ruin_ui_platform_wayland = { path = "../ui_platform_wayland" }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
ruin_ui_platform_macos = { path = "../ui_platform_macos" }
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = [
|
||||
"env-filter",
|
||||
|
||||
@@ -4,8 +4,7 @@ use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::{EnvFilter, fmt};
|
||||
|
||||
fn install_tracing() {
|
||||
let filter = EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new("warn"));
|
||||
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));
|
||||
let fmt_layer = fmt::layer()
|
||||
.with_target(true)
|
||||
.with_thread_ids(true)
|
||||
|
||||
@@ -259,7 +259,12 @@ fn CardFrame(title: View, toolbar: ChildViews, children: ChildViews) -> impl Int
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn InlineDialog(open: bool, title: View, actions: ChildViews, children: ChildViews) -> impl IntoView {
|
||||
fn InlineDialog(
|
||||
open: bool,
|
||||
title: View,
|
||||
actions: ChildViews,
|
||||
children: ChildViews,
|
||||
) -> impl IntoView {
|
||||
if open {
|
||||
let actions_row = view! {
|
||||
row(gap = 10.0) {
|
||||
|
||||
@@ -11,9 +11,9 @@ use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::future::Future;
|
||||
use std::iter;
|
||||
use std::time::Instant;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::time::Instant;
|
||||
|
||||
use ruin_reactivity::effect;
|
||||
use ruin_runtime::queue_future;
|
||||
@@ -25,6 +25,9 @@ use ruin_ui::{
|
||||
TextSystem, TextWrap, UiSize, WindowController, WindowSpec, WindowUpdate,
|
||||
layout_snapshot_with_cache,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use ruin_ui_platform_macos::start_macos_ui;
|
||||
#[cfg(target_os = "linux")]
|
||||
use ruin_ui_platform_wayland::start_wayland_ui;
|
||||
|
||||
pub use ResourceState::{Pending, Ready};
|
||||
@@ -32,6 +35,16 @@ pub use ruin_app_proc_macros::{component, context_provider, view};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn start_platform_ui() -> ruin_ui::UiRuntime {
|
||||
start_wayland_ui()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn start_platform_ui() -> ruin_ui::UiRuntime {
|
||||
start_macos_ui()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Window {
|
||||
spec: WindowSpec,
|
||||
@@ -149,7 +162,7 @@ impl<M: Mountable> MountedApp<M> {
|
||||
render_state,
|
||||
} = self;
|
||||
|
||||
let mut ui = start_wayland_ui();
|
||||
let mut ui = start_platform_ui();
|
||||
let window = ui.create_window(app_window.spec.clone())?;
|
||||
let initial_viewport = app_window
|
||||
.spec
|
||||
@@ -378,7 +391,7 @@ impl<M: Mountable> MountedApp<M> {
|
||||
}
|
||||
|
||||
fn handle_text_selection_event(
|
||||
window: &WindowController,
|
||||
_window: &WindowController,
|
||||
interaction_tree: &InteractionTree,
|
||||
event: &RoutedPointerEvent,
|
||||
text_selection: &TextSelectionState,
|
||||
@@ -439,7 +452,8 @@ impl<M: Mountable> MountedApp<M> {
|
||||
|
||||
if selection_changed {
|
||||
text_selection.version.update(|value| *value += 1);
|
||||
sync_primary_selection(window, interaction_tree, *text_selection.selection.borrow())?;
|
||||
#[cfg(target_os = "linux")]
|
||||
sync_primary_selection(_window, interaction_tree, *text_selection.selection.borrow())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1848,6 +1862,7 @@ fn apply_text_selection_overlay(
|
||||
scene.items = next_items;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn sync_primary_selection(
|
||||
window: &WindowController,
|
||||
interaction_tree: &InteractionTree,
|
||||
@@ -1983,8 +1998,9 @@ pub mod prelude {
|
||||
Pending, Ready, Resource, ResourceState, Result, ScrollBoxBuilder, ScrollBoxWidget,
|
||||
Shortcut, ShortcutScope, Signal, TextBuilder, TextChildren, TextRole, TextValue, View,
|
||||
WidgetRef, Window, block, button, colors, column, component, context_provider, provide,
|
||||
row, scroll_box, surfaces, text, use_context, use_effect, use_memo, use_resource, use_shortcut,
|
||||
use_shortcut_with_context, use_signal, use_widget_ref, use_window_title, view,
|
||||
row, scroll_box, surfaces, text, use_context, use_effect, use_memo, use_resource,
|
||||
use_shortcut, use_shortcut_with_context, use_signal, use_widget_ref, use_window_title,
|
||||
view,
|
||||
};
|
||||
pub use ruin_ui::{
|
||||
Border, Color, CursorIcon, Edges, Element, ElementId, InteractionTree, PointerButton,
|
||||
|
||||
@@ -60,10 +60,7 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
|
||||
Some(prop) if prop.ident == "children" => {
|
||||
let prop = inputs.pop().expect("last input should exist");
|
||||
let kind = parse_children_contract_kind(&prop.ty)?;
|
||||
Some(ChildContract {
|
||||
ty: prop.ty,
|
||||
kind,
|
||||
})
|
||||
Some(ChildContract { ty: prop.ty, kind })
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
@@ -87,7 +84,8 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
|
||||
}
|
||||
Some(contract) => {
|
||||
let child_arg_ty = child_builder_arg_type_tokens(contract.kind);
|
||||
let child_value = wrap_special_value_tokens(contract.kind, &format_ident!("children"));
|
||||
let child_value =
|
||||
wrap_special_value_tokens(contract.kind, &format_ident!("children"));
|
||||
let child_field_ident = child_field_ident
|
||||
.as_ref()
|
||||
.expect("child field ident should exist");
|
||||
@@ -574,10 +572,7 @@ fn child_builder_arg_type_tokens(kind: SpecialValueKind) -> proc_macro2::TokenSt
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_special_value_tokens(
|
||||
kind: SpecialValueKind,
|
||||
ident: &Ident,
|
||||
) -> proc_macro2::TokenStream {
|
||||
fn wrap_special_value_tokens(kind: SpecialValueKind, ident: &Ident) -> proc_macro2::TokenStream {
|
||||
match kind {
|
||||
SpecialValueKind::Plain => quote! { #ident },
|
||||
SpecialValueKind::ChildViews => quote! { ::ruin_app::ChildViews::from_children(#ident) },
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex};
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use crate::op::completion::{CompletionFuture, CompletionHandle};
|
||||
use crate::sys::linux::channel::runtime_waiter;
|
||||
use crate::sys::current::channel::runtime_waiter;
|
||||
|
||||
/// Creates a bounded channel with room for at most `capacity` queued messages.
|
||||
///
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex};
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use crate::op::completion::{CompletionFuture, CompletionHandle};
|
||||
use crate::sys::linux::channel::runtime_waiter;
|
||||
use crate::sys::current::channel::runtime_waiter;
|
||||
|
||||
/// Creates a single-use channel for transferring one value from a [`Sender`] to a [`Receiver`].
|
||||
///
|
||||
|
||||
@@ -3,44 +3,9 @@
|
||||
use std::io;
|
||||
use std::os::fd::RawFd;
|
||||
|
||||
use crate::op::completion::completion_for_current_thread;
|
||||
use crate::platform::linux_x86_64::runtime::with_current_driver;
|
||||
use crate::platform::linux_x86_64::uring::{IORING_OP_POLL_ADD, IoUringCqe};
|
||||
|
||||
/// Waits until `fd` becomes readable or reports an error/hangup condition.
|
||||
pub async fn wait_readable(fd: RawFd) -> io::Result<()> {
|
||||
submit_poll(fd, libc::POLLIN | libc::POLLERR | libc::POLLHUP).await
|
||||
}
|
||||
|
||||
async fn submit_poll(fd: RawFd, mask: i16) -> io::Result<()> {
|
||||
let (future, handle) = completion_for_current_thread::<io::Result<()>>();
|
||||
let callback_handle = handle.clone();
|
||||
let token = with_current_driver(|driver| {
|
||||
driver.submit_operation(
|
||||
move |sqe| {
|
||||
sqe.opcode = IORING_OP_POLL_ADD;
|
||||
sqe.fd = fd;
|
||||
sqe.len = 0;
|
||||
sqe.op_flags = mask as u32;
|
||||
},
|
||||
move |cqe| {
|
||||
callback_handle.complete(cqe_to_result(cqe));
|
||||
},
|
||||
)
|
||||
})?;
|
||||
|
||||
handle.set_cancel(move || {
|
||||
let _ = with_current_driver(|driver| driver.cancel_operation(token));
|
||||
});
|
||||
|
||||
future.await
|
||||
}
|
||||
|
||||
fn cqe_to_result(cqe: IoUringCqe) -> io::Result<()> {
|
||||
if cqe.res < 0 {
|
||||
return Err(io::Error::from_raw_os_error(-cqe.res));
|
||||
}
|
||||
Ok(())
|
||||
crate::sys::current::fd::wait_readable(fd).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -53,8 +18,8 @@ mod tests {
|
||||
#[test]
|
||||
fn wait_readable_resolves_for_pipe() {
|
||||
let mut fds = [0; 2];
|
||||
let result = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC | libc::O_NONBLOCK) };
|
||||
assert_eq!(result, 0, "pipe2 should succeed");
|
||||
let result = unsafe { libc::pipe(fds.as_mut_ptr()) };
|
||||
assert_eq!(result, 0, "pipe should succeed");
|
||||
let read_fd = fds[0];
|
||||
let write_fd = fds[1];
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::op::fs::{
|
||||
FileType as RawFileType, FsOp, MetadataTarget, OpenOptions as OpOpenOptions,
|
||||
RawDirEntry as OpDirEntry, RawMetadata,
|
||||
};
|
||||
use crate::sys::linux::fs as sys_fs;
|
||||
use crate::sys::current::fs as sys_fs;
|
||||
|
||||
struct FileInner {
|
||||
fd: OwnedFd,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Runtime, driver, async I/O, and channel primitives for RUIN.
|
||||
//!
|
||||
//! The crate is centered around a single-threaded event loop with explicit worker threads,
|
||||
//! JavaScript-style microtask/macrotask scheduling, and Linux `io_uring`-backed I/O.
|
||||
//! JavaScript-style microtask/macrotask scheduling and platform-specific async I/O backends.
|
||||
//!
|
||||
//! Most users will start with:
|
||||
//!
|
||||
@@ -11,17 +11,22 @@
|
||||
//!
|
||||
//! # Platform support
|
||||
//!
|
||||
//! `ruin-runtime` currently targets Linux on `x86_64`.
|
||||
//! `ruin-runtime` currently targets:
|
||||
//! - Linux `x86_64`
|
||||
//! - macOS `aarch64`
|
||||
//!
|
||||
//! RUIN runtime foundations.
|
||||
//!
|
||||
//! This crate provides a Linux x86_64 runtime substrate: the mesh allocator, the driver, and a
|
||||
//! single-threaded runtime loop with worker-thread task forwarding.
|
||||
//! This crate provides a platform runtime substrate with a single-threaded runtime loop and
|
||||
//! worker-thread task forwarding.
|
||||
|
||||
#![feature(thread_local)]
|
||||
|
||||
#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
|
||||
compile_error!("ruin-runtime currently supports only Linux x86_64.");
|
||||
#[cfg(not(any(
|
||||
all(target_os = "linux", target_arch = "x86_64"),
|
||||
all(target_os = "macos", target_arch = "aarch64")
|
||||
)))]
|
||||
compile_error!("ruin-runtime currently supports Linux x86_64 and macOS aarch64.");
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
@@ -56,11 +61,24 @@ pub use ruin_runtime_proc_macros::async_main;
|
||||
/// thread before calling [`run`].
|
||||
pub use ruin_runtime_proc_macros::main;
|
||||
|
||||
/// Driver primitives re-exported from the Linux x86_64 backend.
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub use platform::linux_x86_64::driver::{
|
||||
/// Driver primitives re-exported from the active backend.
|
||||
#[cfg(any(
|
||||
all(target_os = "linux", target_arch = "x86_64"),
|
||||
all(target_os = "macos", target_arch = "aarch64")
|
||||
))]
|
||||
pub use platform::current::driver::{
|
||||
Driver, ReadyEvents, ThreadNotifier, create, create_driver, monotonic_now,
|
||||
};
|
||||
/// Runtime/event-loop primitives re-exported from the active backend.
|
||||
#[cfg(any(
|
||||
all(target_os = "linux", target_arch = "x86_64"),
|
||||
all(target_os = "macos", target_arch = "aarch64")
|
||||
))]
|
||||
pub use platform::current::runtime::{
|
||||
IntervalHandle, JoinHandle, ThreadHandle, TimeoutHandle, WorkerHandle, clear_interval,
|
||||
clear_timeout, current_thread_handle, queue_future, queue_microtask, queue_task, run,
|
||||
set_interval, set_timeout, spawn_worker, yield_now,
|
||||
};
|
||||
/// Public mesh-allocator surface re-exported from the Linux x86_64 backend.
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub use platform::linux_x86_64::mesh_alloc::{
|
||||
@@ -77,22 +95,17 @@ pub use platform::linux_x86_64::mesh_alloc::{
|
||||
/// Additional allocator helpers re-exported from the Linux x86_64 backend.
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub use platform::linux_x86_64::mesh_alloc::{FreelistId, bitmaps_meshable};
|
||||
/// Runtime/event-loop primitives re-exported from the Linux x86_64 backend.
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub use platform::linux_x86_64::runtime::{
|
||||
IntervalHandle, JoinHandle, ThreadHandle, TimeoutHandle, WorkerHandle, clear_interval,
|
||||
clear_timeout, current_thread_handle, queue_future, queue_microtask, queue_task, run,
|
||||
set_interval, set_timeout, spawn_worker, yield_now,
|
||||
};
|
||||
|
||||
/// Returns the default global mesh allocator configuration for this crate.
|
||||
///
|
||||
/// This is useful when embedding the allocator in a `#[global_allocator]` static.
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub const fn default_global_allocator() -> GlobalMeshAllocator {
|
||||
GlobalMeshAllocator::with_default_config()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
mod tests {
|
||||
use super::{MeshAllocator, page_size};
|
||||
|
||||
|
||||
@@ -72,10 +72,10 @@ impl TcpStream {
|
||||
where
|
||||
A: ToSocketAddrs + Send + 'static,
|
||||
{
|
||||
let addrs = crate::sys::linux::net::resolve_addrs(addr).await?;
|
||||
let addrs = crate::sys::current::net::resolve_addrs(addr).await?;
|
||||
let mut last_error = None;
|
||||
for addr in addrs {
|
||||
match crate::sys::linux::net::connect_stream(addr).await {
|
||||
match crate::sys::current::net::connect_stream(addr).await {
|
||||
Ok(fd) => return Ok(Self::from_owned_fd(fd)),
|
||||
Err(error) => last_error = Some(error),
|
||||
}
|
||||
@@ -92,7 +92,7 @@ impl TcpStream {
|
||||
/// Connects to `addr`, failing if the deadline elapses first.
|
||||
pub async fn connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result<Self> {
|
||||
validate_timeout(timeout)?;
|
||||
crate::sys::linux::net::connect_stream_timeout(*addr, timeout)
|
||||
crate::sys::current::net::connect_stream_timeout(*addr, timeout)
|
||||
.await
|
||||
.map(Self::from_owned_fd)
|
||||
}
|
||||
@@ -101,10 +101,10 @@ impl TcpStream {
|
||||
pub async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let data = match self.read_timeout_value() {
|
||||
Some(timeout) => {
|
||||
crate::sys::linux::net::recv_timeout(self.raw_fd(), buf.len(), 0, timeout).await?
|
||||
crate::sys::current::net::recv_timeout(self.raw_fd(), buf.len(), 0, timeout).await?
|
||||
}
|
||||
None => {
|
||||
crate::sys::linux::net::recv(NetOp::Recv {
|
||||
crate::sys::current::net::recv(NetOp::Recv {
|
||||
fd: self.raw_fd(),
|
||||
len: buf.len(),
|
||||
flags: 0,
|
||||
@@ -136,10 +136,11 @@ impl TcpStream {
|
||||
pub async fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match self.write_timeout_value() {
|
||||
Some(timeout) => {
|
||||
crate::sys::linux::net::send_timeout(self.raw_fd(), buf.to_vec(), 0, timeout).await
|
||||
crate::sys::current::net::send_timeout(self.raw_fd(), buf.to_vec(), 0, timeout)
|
||||
.await
|
||||
}
|
||||
None => {
|
||||
crate::sys::linux::net::send(NetOp::Send {
|
||||
crate::sys::current::net::send(NetOp::Send {
|
||||
fd: self.raw_fd(),
|
||||
data: buf.to_vec(),
|
||||
flags: 0,
|
||||
@@ -166,7 +167,7 @@ impl TcpStream {
|
||||
|
||||
/// Shuts down the read, write, or both halves of the connection.
|
||||
pub async fn shutdown(&self, how: Shutdown) -> io::Result<()> {
|
||||
crate::sys::linux::net::shutdown(NetOp::Shutdown {
|
||||
crate::sys::current::net::shutdown(NetOp::Shutdown {
|
||||
fd: self.raw_fd(),
|
||||
how,
|
||||
})
|
||||
@@ -175,39 +176,39 @@ impl TcpStream {
|
||||
|
||||
/// Duplicates the underlying stream socket.
|
||||
pub async fn try_clone(&self) -> io::Result<Self> {
|
||||
crate::sys::linux::net::duplicate(self.raw_fd())
|
||||
crate::sys::current::net::duplicate(self.raw_fd())
|
||||
.await
|
||||
.map(Self::from_owned_fd)
|
||||
}
|
||||
|
||||
/// Returns the local socket address of this stream.
|
||||
pub fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
crate::sys::linux::net::local_addr(self.raw_fd())
|
||||
crate::sys::current::net::local_addr(self.raw_fd())
|
||||
}
|
||||
|
||||
/// Returns the remote peer address of this stream.
|
||||
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||
crate::sys::linux::net::peer_addr(self.raw_fd())
|
||||
crate::sys::current::net::peer_addr(self.raw_fd())
|
||||
}
|
||||
|
||||
/// Reads the current `TCP_NODELAY` setting.
|
||||
pub fn nodelay(&self) -> io::Result<bool> {
|
||||
crate::sys::linux::net::nodelay(self.raw_fd())
|
||||
crate::sys::current::net::nodelay(self.raw_fd())
|
||||
}
|
||||
|
||||
/// Enables or disables `TCP_NODELAY`.
|
||||
pub fn set_nodelay(&self, enabled: bool) -> io::Result<()> {
|
||||
crate::sys::linux::net::set_nodelay(self.raw_fd(), enabled)
|
||||
crate::sys::current::net::set_nodelay(self.raw_fd(), enabled)
|
||||
}
|
||||
|
||||
/// Reads the socket's IP time-to-live value.
|
||||
pub fn ttl(&self) -> io::Result<u32> {
|
||||
crate::sys::linux::net::ttl(self.raw_fd())
|
||||
crate::sys::current::net::ttl(self.raw_fd())
|
||||
}
|
||||
|
||||
/// Sets the socket's IP time-to-live value.
|
||||
pub fn set_ttl(&self, ttl: u32) -> io::Result<()> {
|
||||
crate::sys::linux::net::set_ttl(self.raw_fd(), ttl)
|
||||
crate::sys::current::net::set_ttl(self.raw_fd(), ttl)
|
||||
}
|
||||
|
||||
/// Returns the read timeout used by async read operations on this handle.
|
||||
@@ -269,10 +270,10 @@ impl TcpListener {
|
||||
where
|
||||
A: ToSocketAddrs + Send + 'static,
|
||||
{
|
||||
let addrs = crate::sys::linux::net::resolve_addrs(addr).await?;
|
||||
let addrs = crate::sys::current::net::resolve_addrs(addr).await?;
|
||||
let mut last_error = None;
|
||||
for addr in addrs {
|
||||
match crate::sys::linux::net::bind_listener(addr, None).await {
|
||||
match crate::sys::current::net::bind_listener(addr, None).await {
|
||||
Ok(fd) => return Ok(Self::from_owned_fd(fd)),
|
||||
Err(error) => last_error = Some(error),
|
||||
}
|
||||
@@ -288,7 +289,8 @@ impl TcpListener {
|
||||
|
||||
/// Accepts an incoming connection.
|
||||
pub async fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> {
|
||||
let accepted = crate::sys::linux::net::accept(NetOp::Accept { fd: self.raw_fd() }).await?;
|
||||
let accepted =
|
||||
crate::sys::current::net::accept(NetOp::Accept { fd: self.raw_fd() }).await?;
|
||||
|
||||
let stream = TcpStream::from_owned_fd(unsafe { OwnedFd::from_raw_fd(accepted.fd) });
|
||||
Ok((stream, accepted.peer_addr))
|
||||
@@ -296,17 +298,17 @@ impl TcpListener {
|
||||
|
||||
/// Returns the local socket address of this listener.
|
||||
pub fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
crate::sys::linux::net::local_addr(self.raw_fd())
|
||||
crate::sys::current::net::local_addr(self.raw_fd())
|
||||
}
|
||||
|
||||
/// Reads the listener socket's IP time-to-live value.
|
||||
pub fn ttl(&self) -> io::Result<u32> {
|
||||
crate::sys::linux::net::ttl(self.raw_fd())
|
||||
crate::sys::current::net::ttl(self.raw_fd())
|
||||
}
|
||||
|
||||
/// Sets the listener socket's IP time-to-live value.
|
||||
pub fn set_ttl(&self, ttl: u32) -> io::Result<()> {
|
||||
crate::sys::linux::net::set_ttl(self.raw_fd(), ttl)
|
||||
crate::sys::current::net::set_ttl(self.raw_fd(), ttl)
|
||||
}
|
||||
|
||||
fn from_owned_fd(fd: OwnedFd) -> Self {
|
||||
@@ -326,10 +328,10 @@ impl UdpSocket {
|
||||
where
|
||||
A: ToSocketAddrs + Send + 'static,
|
||||
{
|
||||
let addrs = crate::sys::linux::net::resolve_addrs(addr).await?;
|
||||
let addrs = crate::sys::current::net::resolve_addrs(addr).await?;
|
||||
let mut last_error = None;
|
||||
for addr in addrs {
|
||||
match crate::sys::linux::net::bind_datagram(addr).await {
|
||||
match crate::sys::current::net::bind_datagram(addr).await {
|
||||
Ok(fd) => return Ok(Self::from_owned_fd(fd)),
|
||||
Err(error) => last_error = Some(error),
|
||||
}
|
||||
@@ -351,10 +353,10 @@ impl UdpSocket {
|
||||
where
|
||||
A: ToSocketAddrs + Send + 'static,
|
||||
{
|
||||
let addrs = crate::sys::linux::net::resolve_addrs(addr).await?;
|
||||
let addrs = crate::sys::current::net::resolve_addrs(addr).await?;
|
||||
let mut last_error = None;
|
||||
for addr in addrs {
|
||||
match crate::sys::linux::net::connect(NetOp::Connect {
|
||||
match crate::sys::current::net::connect(NetOp::Connect {
|
||||
fd: self.raw_fd(),
|
||||
addr,
|
||||
})
|
||||
@@ -377,10 +379,11 @@ impl UdpSocket {
|
||||
pub async fn send(&self, buf: &[u8]) -> io::Result<usize> {
|
||||
match self.write_timeout_value() {
|
||||
Some(timeout) => {
|
||||
crate::sys::linux::net::send_timeout(self.raw_fd(), buf.to_vec(), 0, timeout).await
|
||||
crate::sys::current::net::send_timeout(self.raw_fd(), buf.to_vec(), 0, timeout)
|
||||
.await
|
||||
}
|
||||
None => {
|
||||
crate::sys::linux::net::send(NetOp::Send {
|
||||
crate::sys::current::net::send(NetOp::Send {
|
||||
fd: self.raw_fd(),
|
||||
data: buf.to_vec(),
|
||||
flags: 0,
|
||||
@@ -394,10 +397,10 @@ impl UdpSocket {
|
||||
pub async fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let data = match self.read_timeout_value() {
|
||||
Some(timeout) => {
|
||||
crate::sys::linux::net::recv_timeout(self.raw_fd(), buf.len(), 0, timeout).await?
|
||||
crate::sys::current::net::recv_timeout(self.raw_fd(), buf.len(), 0, timeout).await?
|
||||
}
|
||||
None => {
|
||||
crate::sys::linux::net::recv(NetOp::Recv {
|
||||
crate::sys::current::net::recv(NetOp::Recv {
|
||||
fd: self.raw_fd(),
|
||||
len: buf.len(),
|
||||
flags: 0,
|
||||
@@ -414,7 +417,7 @@ impl UdpSocket {
|
||||
pub async fn peek(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let data = match self.read_timeout_value() {
|
||||
Some(timeout) => {
|
||||
crate::sys::linux::net::recv_timeout(
|
||||
crate::sys::current::net::recv_timeout(
|
||||
self.raw_fd(),
|
||||
buf.len(),
|
||||
libc::MSG_PEEK,
|
||||
@@ -423,7 +426,7 @@ impl UdpSocket {
|
||||
.await?
|
||||
}
|
||||
None => {
|
||||
crate::sys::linux::net::recv(NetOp::Recv {
|
||||
crate::sys::current::net::recv(NetOp::Recv {
|
||||
fd: self.raw_fd(),
|
||||
len: buf.len(),
|
||||
flags: libc::MSG_PEEK,
|
||||
@@ -441,13 +444,13 @@ impl UdpSocket {
|
||||
where
|
||||
A: ToSocketAddrs + Send + 'static,
|
||||
{
|
||||
let addrs = crate::sys::linux::net::resolve_addrs(addr).await?;
|
||||
let addrs = crate::sys::current::net::resolve_addrs(addr).await?;
|
||||
let mut last_error = None;
|
||||
let timeout = self.write_timeout_value();
|
||||
for addr in addrs {
|
||||
let result = match timeout {
|
||||
Some(timeout) => {
|
||||
crate::sys::linux::net::send_to_timeout(
|
||||
crate::sys::current::net::send_to_timeout(
|
||||
self.raw_fd(),
|
||||
buf.to_vec(),
|
||||
addr,
|
||||
@@ -457,7 +460,7 @@ impl UdpSocket {
|
||||
.await
|
||||
}
|
||||
None => {
|
||||
crate::sys::linux::net::send_to(NetOp::SendTo {
|
||||
crate::sys::current::net::send_to(NetOp::SendTo {
|
||||
fd: self.raw_fd(),
|
||||
target: addr,
|
||||
data: buf.to_vec(),
|
||||
@@ -484,11 +487,11 @@ impl UdpSocket {
|
||||
pub async fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
|
||||
let datagram = match self.read_timeout_value() {
|
||||
Some(timeout) => {
|
||||
crate::sys::linux::net::recv_from_timeout(self.raw_fd(), buf.len(), 0, timeout)
|
||||
crate::sys::current::net::recv_from_timeout(self.raw_fd(), buf.len(), 0, timeout)
|
||||
.await?
|
||||
}
|
||||
None => {
|
||||
crate::sys::linux::net::recv_from(NetOp::RecvFrom {
|
||||
crate::sys::current::net::recv_from(NetOp::RecvFrom {
|
||||
fd: self.raw_fd(),
|
||||
len: buf.len(),
|
||||
flags: 0,
|
||||
@@ -505,7 +508,7 @@ impl UdpSocket {
|
||||
pub async fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
|
||||
let datagram = match self.read_timeout_value() {
|
||||
Some(timeout) => {
|
||||
crate::sys::linux::net::recv_from_timeout(
|
||||
crate::sys::current::net::recv_from_timeout(
|
||||
self.raw_fd(),
|
||||
buf.len(),
|
||||
libc::MSG_PEEK,
|
||||
@@ -514,7 +517,7 @@ impl UdpSocket {
|
||||
.await?
|
||||
}
|
||||
None => {
|
||||
crate::sys::linux::net::recv_from(NetOp::RecvFrom {
|
||||
crate::sys::current::net::recv_from(NetOp::RecvFrom {
|
||||
fd: self.raw_fd(),
|
||||
len: buf.len(),
|
||||
flags: libc::MSG_PEEK,
|
||||
@@ -529,39 +532,39 @@ impl UdpSocket {
|
||||
|
||||
/// Duplicates the underlying UDP socket.
|
||||
pub async fn try_clone(&self) -> io::Result<Self> {
|
||||
crate::sys::linux::net::duplicate(self.raw_fd())
|
||||
crate::sys::current::net::duplicate(self.raw_fd())
|
||||
.await
|
||||
.map(Self::from_owned_fd)
|
||||
}
|
||||
|
||||
/// Returns the local socket address of this socket.
|
||||
pub fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
crate::sys::linux::net::local_addr(self.raw_fd())
|
||||
crate::sys::current::net::local_addr(self.raw_fd())
|
||||
}
|
||||
|
||||
/// Returns the connected peer address, if the socket has been connected.
|
||||
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||
crate::sys::linux::net::peer_addr(self.raw_fd())
|
||||
crate::sys::current::net::peer_addr(self.raw_fd())
|
||||
}
|
||||
|
||||
/// Reads the `SO_BROADCAST` setting.
|
||||
pub fn broadcast(&self) -> io::Result<bool> {
|
||||
crate::sys::linux::net::broadcast(self.raw_fd())
|
||||
crate::sys::current::net::broadcast(self.raw_fd())
|
||||
}
|
||||
|
||||
/// Enables or disables `SO_BROADCAST`.
|
||||
pub fn set_broadcast(&self, enabled: bool) -> io::Result<()> {
|
||||
crate::sys::linux::net::set_broadcast(self.raw_fd(), enabled)
|
||||
crate::sys::current::net::set_broadcast(self.raw_fd(), enabled)
|
||||
}
|
||||
|
||||
/// Reads the socket's IP time-to-live value.
|
||||
pub fn ttl(&self) -> io::Result<u32> {
|
||||
crate::sys::linux::net::ttl(self.raw_fd())
|
||||
crate::sys::current::net::ttl(self.raw_fd())
|
||||
}
|
||||
|
||||
/// Sets the socket's IP time-to-live value.
|
||||
pub fn set_ttl(&self, ttl: u32) -> io::Result<()> {
|
||||
crate::sys::linux::net::set_ttl(self.raw_fd(), ttl)
|
||||
crate::sys::current::net::set_ttl(self.raw_fd(), ttl)
|
||||
}
|
||||
|
||||
/// Returns the read timeout used by async receive operations on this handle.
|
||||
@@ -627,13 +630,13 @@ impl HyperRead for TcpStream {
|
||||
|
||||
if this.pending_read.is_none() {
|
||||
this.pending_read = Some(match this.read_timeout_value() {
|
||||
Some(timeout) => Box::pin(crate::sys::linux::net::recv_timeout(
|
||||
Some(timeout) => Box::pin(crate::sys::current::net::recv_timeout(
|
||||
this.raw_fd(),
|
||||
buf.remaining(),
|
||||
0,
|
||||
timeout,
|
||||
)),
|
||||
None => crate::sys::linux::net::recv_future(this.raw_fd(), buf.remaining()),
|
||||
None => crate::sys::current::net::recv_future(this.raw_fd(), buf.remaining()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -671,13 +674,13 @@ impl HyperWrite for TcpStream {
|
||||
|
||||
if this.pending_write.is_none() {
|
||||
this.pending_write = Some(match this.write_timeout_value() {
|
||||
Some(timeout) => Box::pin(crate::sys::linux::net::send_timeout(
|
||||
Some(timeout) => Box::pin(crate::sys::current::net::send_timeout(
|
||||
this.raw_fd(),
|
||||
buf.to_vec(),
|
||||
0,
|
||||
timeout,
|
||||
)),
|
||||
None => crate::sys::linux::net::send_future(this.raw_fd(), buf.to_vec()),
|
||||
None => crate::sys::current::net::send_future(this.raw_fd(), buf.to_vec()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -707,7 +710,7 @@ impl HyperWrite for TcpStream {
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
let this = self.get_mut();
|
||||
if this.pending_shutdown.is_none() {
|
||||
this.pending_shutdown = Some(crate::sys::linux::net::shutdown_future(
|
||||
this.pending_shutdown = Some(crate::sys::current::net::shutdown_future(
|
||||
this.raw_fd(),
|
||||
Shutdown::Write,
|
||||
));
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::task::{Context, Poll, Waker};
|
||||
|
||||
use crate::platform::linux_x86_64::runtime::{ThreadHandle, current_thread_handle};
|
||||
use crate::platform::current::runtime::{ThreadHandle, current_thread_handle};
|
||||
|
||||
type CancelCallback = Box<dyn FnOnce() + Send + 'static>;
|
||||
|
||||
|
||||
@@ -1178,7 +1178,11 @@ fn schedule_immediate_interval(owner: *const ThreadState, id: usize) {
|
||||
};
|
||||
(callback.borrow_mut())();
|
||||
// Re-enqueue for the next turn if still live.
|
||||
if current_thread().immediate_intervals.borrow().contains_key(&id) {
|
||||
if current_thread()
|
||||
.immediate_intervals
|
||||
.borrow()
|
||||
.contains_key(&id)
|
||||
{
|
||||
schedule_immediate_interval(owner, id);
|
||||
}
|
||||
}));
|
||||
@@ -1207,12 +1211,8 @@ fn dispatch_expired_timers() {
|
||||
.deadline
|
||||
.checked_add(interval)
|
||||
.unwrap_or(Duration::MAX);
|
||||
let next_timer = TimerNode::interval(
|
||||
timer.id,
|
||||
next_deadline,
|
||||
interval,
|
||||
Rc::clone(&callback),
|
||||
);
|
||||
let next_timer =
|
||||
TimerNode::interval(timer.id, next_deadline, interval, Rc::clone(&callback));
|
||||
current_thread().timers.borrow_mut().insert(next_timer);
|
||||
|
||||
push_local_macrotask(Box::new(move || {
|
||||
@@ -1394,8 +1394,7 @@ mod tests {
|
||||
// turns by awaiting a sleep between checks.
|
||||
let count = Rc::new(Cell::new(0usize));
|
||||
let count_clone = Rc::clone(&count);
|
||||
let handle_slot: Rc<RefCell<Option<super::IntervalHandle>>> =
|
||||
Rc::new(RefCell::new(None));
|
||||
let handle_slot: Rc<RefCell<Option<super::IntervalHandle>>> = Rc::new(RefCell::new(None));
|
||||
let handle_slot_clone = Rc::clone(&handle_slot);
|
||||
|
||||
let handle = set_interval(Duration::ZERO, move || {
|
||||
|
||||
340
lib/runtime/src/platform/macos_aarch64/driver.rs
Normal file
340
lib/runtime/src/platform/macos_aarch64/driver.rs
Normal file
@@ -0,0 +1,340 @@
|
||||
//! Public runtime driver primitives for macOS.
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::io;
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct NotifierInner {
|
||||
write_fd: RawFd,
|
||||
closed: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl NotifierInner {
|
||||
fn notify(&self) -> io::Result<()> {
|
||||
if self.closed.load(Ordering::Acquire) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::BrokenPipe,
|
||||
"target runtime driver is closed",
|
||||
));
|
||||
}
|
||||
|
||||
let byte = 1u8;
|
||||
let written = unsafe {
|
||||
libc::write(
|
||||
self.write_fd,
|
||||
&byte as *const u8 as *const libc::c_void,
|
||||
std::mem::size_of::<u8>(),
|
||||
)
|
||||
};
|
||||
if written < 0 {
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() == io::ErrorKind::WouldBlock {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Cross-thread notifier for a runtime thread's driver.
|
||||
pub struct ThreadNotifier {
|
||||
inner: NotifierInner,
|
||||
}
|
||||
|
||||
impl ThreadNotifier {
|
||||
/// Sends a wake notification to the target runtime thread.
|
||||
pub fn notify(&self) -> io::Result<()> {
|
||||
self.inner.notify()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
|
||||
/// Readiness information returned by [`Driver::poll`].
|
||||
pub struct ReadyEvents {
|
||||
/// One or more timer expirations are pending.
|
||||
pub timer: bool,
|
||||
/// One or more cross-thread wake notifications are pending.
|
||||
pub wake: bool,
|
||||
}
|
||||
|
||||
/// Low-level macOS runtime driver backed by `kqueue` and a wake pipe.
|
||||
pub struct Driver {
|
||||
kqueue_fd: RawFd,
|
||||
wake_read_fd: RawFd,
|
||||
wake_write_fd: RawFd,
|
||||
closed: Arc<AtomicBool>,
|
||||
timer_deadline: Cell<Option<Duration>>,
|
||||
pending_wakes: Cell<u64>,
|
||||
pending_timers: Cell<u64>,
|
||||
}
|
||||
|
||||
/// Creates a new driver and its paired [`ThreadNotifier`].
|
||||
pub fn create() -> io::Result<(Driver, ThreadNotifier)> {
|
||||
create_driver()
|
||||
}
|
||||
|
||||
/// Creates a new driver and its paired [`ThreadNotifier`].
|
||||
pub fn create_driver() -> io::Result<(Driver, ThreadNotifier)> {
|
||||
let kqueue_fd = cvt(unsafe { libc::kqueue() })?;
|
||||
|
||||
let mut pipe_fds = [0; 2];
|
||||
cvt(unsafe { libc::pipe(pipe_fds.as_mut_ptr()) })?;
|
||||
let wake_read_fd = pipe_fds[0];
|
||||
let wake_write_fd = pipe_fds[1];
|
||||
|
||||
set_nonblocking(wake_read_fd)?;
|
||||
set_nonblocking(wake_write_fd)?;
|
||||
|
||||
let event = libc::kevent {
|
||||
ident: wake_read_fd as usize,
|
||||
filter: libc::EVFILT_READ,
|
||||
flags: libc::EV_ADD | libc::EV_ENABLE,
|
||||
fflags: 0,
|
||||
data: 0,
|
||||
udata: std::ptr::null_mut(),
|
||||
};
|
||||
|
||||
let submitted = unsafe {
|
||||
libc::kevent(
|
||||
kqueue_fd,
|
||||
&event,
|
||||
1,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if submitted < 0 {
|
||||
let error = io::Error::last_os_error();
|
||||
unsafe {
|
||||
libc::close(wake_read_fd);
|
||||
libc::close(wake_write_fd);
|
||||
libc::close(kqueue_fd);
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let closed = Arc::new(AtomicBool::new(false));
|
||||
let driver = Driver {
|
||||
kqueue_fd,
|
||||
wake_read_fd,
|
||||
wake_write_fd,
|
||||
closed: Arc::clone(&closed),
|
||||
timer_deadline: Cell::new(None),
|
||||
pending_wakes: Cell::new(0),
|
||||
pending_timers: Cell::new(0),
|
||||
};
|
||||
|
||||
let notifier = ThreadNotifier {
|
||||
inner: NotifierInner {
|
||||
write_fd: wake_write_fd,
|
||||
closed,
|
||||
},
|
||||
};
|
||||
|
||||
Ok((driver, notifier))
|
||||
}
|
||||
|
||||
impl Driver {
|
||||
pub(crate) fn bind_current_thread(&self) {}
|
||||
|
||||
pub(crate) fn unbind_current_thread(&self) {}
|
||||
|
||||
/// Polls the driver without blocking.
|
||||
pub fn poll(&self) -> io::Result<Option<ReadyEvents>> {
|
||||
let mut pending = ReadyEvents::default();
|
||||
if self.pending_wakes.get() > 0 {
|
||||
pending.wake = true;
|
||||
}
|
||||
if self.pending_timers.get() > 0 {
|
||||
pending.timer = true;
|
||||
}
|
||||
if pending.wake || pending.timer {
|
||||
return Ok(Some(pending));
|
||||
}
|
||||
self.process(Some(Duration::ZERO))
|
||||
}
|
||||
|
||||
/// Blocks until at least one event is available.
|
||||
pub fn wait(&self) -> io::Result<()> {
|
||||
let now = monotonic_now()?;
|
||||
let timeout = self
|
||||
.timer_deadline
|
||||
.get()
|
||||
.map(|deadline| deadline.saturating_sub(now));
|
||||
let _ = self.process(timeout)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the currently armed timer deadline.
|
||||
pub fn rearm_timer(&self, deadline: Option<Duration>) -> io::Result<()> {
|
||||
self.timer_deadline.set(deadline);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Drains the accumulated wake notification count.
|
||||
pub fn drain_wake(&self) -> io::Result<u64> {
|
||||
let wakes = self.pending_wakes.replace(0);
|
||||
if wakes == 0 {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::WouldBlock,
|
||||
"no wake events are pending",
|
||||
))
|
||||
} else {
|
||||
Ok(wakes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Drains the accumulated timer-expiration count.
|
||||
pub fn drain_timer(&self) -> io::Result<u64> {
|
||||
let timers = self.pending_timers.replace(0);
|
||||
if timers == 0 {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::WouldBlock,
|
||||
"no timer events are pending",
|
||||
))
|
||||
} else {
|
||||
Ok(timers)
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&self, timeout: Option<Duration>) -> io::Result<Option<ReadyEvents>> {
|
||||
let mut ready = ReadyEvents::default();
|
||||
|
||||
let mut events = [unsafe { std::mem::zeroed::<libc::kevent>() }; 16];
|
||||
let timeout_spec = timeout_to_timespec(timeout);
|
||||
let timeout_ptr = timeout_spec
|
||||
.as_ref()
|
||||
.map_or(std::ptr::null(), |value| value as *const libc::timespec);
|
||||
|
||||
let result = unsafe {
|
||||
libc::kevent(
|
||||
self.kqueue_fd,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
events.as_mut_ptr(),
|
||||
events.len() as i32,
|
||||
timeout_ptr,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() != io::ErrorKind::Interrupted {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
let mut saw_any = false;
|
||||
let count = result.max(0) as usize;
|
||||
if count > 0 {
|
||||
saw_any = true;
|
||||
for event in events.iter().take(count) {
|
||||
if event.ident as RawFd == self.wake_read_fd {
|
||||
ready.wake = true;
|
||||
let wakes = drain_wake_pipe(self.wake_read_fd)?;
|
||||
self.pending_wakes
|
||||
.set(self.pending_wakes.get().saturating_add(wakes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(deadline) = self.timer_deadline.get()
|
||||
&& monotonic_now()? >= deadline
|
||||
{
|
||||
ready.timer = true;
|
||||
saw_any = true;
|
||||
self.timer_deadline.set(None);
|
||||
self.pending_timers
|
||||
.set(self.pending_timers.get().saturating_add(1));
|
||||
}
|
||||
|
||||
if saw_any { Ok(Some(ready)) } else { Ok(None) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Driver {
|
||||
fn drop(&mut self) {
|
||||
self.closed.store(true, Ordering::Release);
|
||||
unsafe {
|
||||
libc::close(self.wake_read_fd);
|
||||
libc::close(self.wake_write_fd);
|
||||
libc::close(self.kqueue_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current monotonic clock reading.
|
||||
pub fn monotonic_now() -> io::Result<Duration> {
|
||||
let mut now = std::mem::MaybeUninit::<libc::timespec>::uninit();
|
||||
let result = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, now.as_mut_ptr()) };
|
||||
if result < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let now = unsafe { now.assume_init() };
|
||||
Ok(Duration::new(now.tv_sec as u64, now.tv_nsec as u32))
|
||||
}
|
||||
|
||||
fn cvt(value: libc::c_int) -> io::Result<libc::c_int> {
|
||||
if value < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_nonblocking(fd: RawFd) -> io::Result<()> {
|
||||
let flags = cvt(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
|
||||
cvt(unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn timeout_to_timespec(timeout: Option<Duration>) -> Option<libc::timespec> {
|
||||
timeout.map(|value| libc::timespec {
|
||||
tv_sec: value.as_secs() as libc::time_t,
|
||||
tv_nsec: value.subsec_nanos() as libc::c_long,
|
||||
})
|
||||
}
|
||||
|
||||
fn drain_wake_pipe(fd: RawFd) -> io::Result<u64> {
|
||||
let mut wakes = 0u64;
|
||||
let mut buf = [0u8; 256];
|
||||
|
||||
loop {
|
||||
let read = unsafe {
|
||||
libc::read(
|
||||
fd,
|
||||
buf.as_mut_ptr() as *mut libc::c_void,
|
||||
buf.len() as libc::size_t,
|
||||
)
|
||||
};
|
||||
|
||||
if read > 0 {
|
||||
wakes = wakes.saturating_add(read as u64);
|
||||
continue;
|
||||
}
|
||||
|
||||
if read == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() == io::ErrorKind::WouldBlock {
|
||||
break;
|
||||
}
|
||||
if error.kind() == io::ErrorKind::Interrupted {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
Ok(wakes.max(1))
|
||||
}
|
||||
2
lib/runtime/src/platform/macos_aarch64/mod.rs
Normal file
2
lib/runtime/src/platform/macos_aarch64/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod driver;
|
||||
pub mod runtime;
|
||||
1416
lib/runtime/src/platform/macos_aarch64/runtime.rs
Normal file
1416
lib/runtime/src/platform/macos_aarch64/runtime.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,9 @@
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub mod linux_x86_64;
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
pub mod macos_aarch64;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub use linux_x86_64 as current;
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
pub use macos_aarch64 as current;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Linux channel wake helpers.
|
||||
|
||||
use crate::op::completion::{CompletionFuture, CompletionHandle, completion};
|
||||
use crate::platform::linux_x86_64::runtime::try_current_thread_handle;
|
||||
use crate::platform::current::runtime::try_current_thread_handle;
|
||||
|
||||
pub(crate) fn runtime_waiter<T: Send + 'static>() -> (CompletionFuture<T>, CompletionHandle<T>) {
|
||||
let owner = try_current_thread_handle()
|
||||
|
||||
44
lib/runtime/src/sys/linux/fd.rs
Normal file
44
lib/runtime/src/sys/linux/fd.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! Linux fd readiness backend.
|
||||
|
||||
use std::io;
|
||||
use std::os::fd::RawFd;
|
||||
|
||||
use crate::op::completion::completion_for_current_thread;
|
||||
use crate::platform::current::runtime::with_current_driver;
|
||||
use crate::platform::linux_x86_64::uring::{IORING_OP_POLL_ADD, IoUringCqe};
|
||||
|
||||
/// Waits until `fd` becomes readable or reports an error/hangup condition.
|
||||
pub async fn wait_readable(fd: RawFd) -> io::Result<()> {
|
||||
submit_poll(fd, libc::POLLIN | libc::POLLERR | libc::POLLHUP).await
|
||||
}
|
||||
|
||||
async fn submit_poll(fd: RawFd, mask: i16) -> io::Result<()> {
|
||||
let (future, handle) = completion_for_current_thread::<io::Result<()>>();
|
||||
let callback_handle = handle.clone();
|
||||
let token = with_current_driver(|driver| {
|
||||
driver.submit_operation(
|
||||
move |sqe| {
|
||||
sqe.opcode = IORING_OP_POLL_ADD;
|
||||
sqe.fd = fd;
|
||||
sqe.len = 0;
|
||||
sqe.op_flags = mask as u32;
|
||||
},
|
||||
move |cqe| {
|
||||
callback_handle.complete(cqe_to_result(cqe));
|
||||
},
|
||||
)
|
||||
})?;
|
||||
|
||||
handle.set_cancel(move || {
|
||||
let _ = with_current_driver(|driver| driver.cancel_operation(token));
|
||||
});
|
||||
|
||||
future.await
|
||||
}
|
||||
|
||||
fn cqe_to_result(cqe: IoUringCqe) -> io::Result<()> {
|
||||
if cqe.res < 0 {
|
||||
return Err(io::Error::from_raw_os_error(-cqe.res));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Linux backend modules.
|
||||
|
||||
pub mod channel;
|
||||
pub mod fd;
|
||||
pub mod fs;
|
||||
pub mod net;
|
||||
|
||||
@@ -803,13 +803,9 @@ where
|
||||
let (future, handle) = completion_for_current_thread::<io::Result<T>>();
|
||||
let callback_handle = handle.clone();
|
||||
let token = with_current_driver(|driver| {
|
||||
driver.submit_operation_with_linked_timeout(
|
||||
fill,
|
||||
timeout,
|
||||
move |cqe| {
|
||||
driver.submit_operation_with_linked_timeout(fill, timeout, move |cqe| {
|
||||
callback_handle.complete(map(cqe));
|
||||
},
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
handle.set_cancel(move || {
|
||||
@@ -1079,7 +1075,6 @@ fn send_sync(fd: RawFd, data: Vec<u8>, flags: i32) -> io::Result<usize> {
|
||||
cvt_long(written).map(|written| written as usize)
|
||||
}
|
||||
|
||||
|
||||
fn recv_sync(fd: RawFd, len: usize, flags: i32) -> io::Result<Vec<u8>> {
|
||||
let mut buffer = vec![0; len];
|
||||
let read = unsafe {
|
||||
@@ -1127,7 +1122,6 @@ fn close_sync(fd: RawFd) -> io::Result<()> {
|
||||
cvt(unsafe { libc::close(fd) }).map(|_| ())
|
||||
}
|
||||
|
||||
|
||||
/// Wrapper making `Box<libc::iovec>` sendable across the async CQE boundary.
|
||||
///
|
||||
/// Safety: `iov_base` points into a `Vec<u8>` that is owned by the same
|
||||
|
||||
10
lib/runtime/src/sys/macos/channel.rs
Normal file
10
lib/runtime/src/sys/macos/channel.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
//! macOS channel wake helpers.
|
||||
|
||||
use crate::op::completion::{CompletionFuture, CompletionHandle, completion};
|
||||
use crate::platform::current::runtime::try_current_thread_handle;
|
||||
|
||||
pub(crate) fn runtime_waiter<T: Send + 'static>() -> (CompletionFuture<T>, CompletionHandle<T>) {
|
||||
let owner = try_current_thread_handle()
|
||||
.expect("async channel operations must be polled on a runtime thread");
|
||||
completion(owner)
|
||||
}
|
||||
43
lib/runtime/src/sys/macos/fd.rs
Normal file
43
lib/runtime/src/sys/macos/fd.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
//! macOS fd readiness backend.
|
||||
|
||||
use std::io;
|
||||
use std::os::fd::RawFd;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::op::completion::completion_for_current_thread;
|
||||
|
||||
/// Waits until `fd` becomes readable or reports an error/hangup condition.
|
||||
pub async fn wait_readable(fd: RawFd) -> io::Result<()> {
|
||||
let (future, handle) = completion_for_current_thread::<io::Result<()>>();
|
||||
std::thread::spawn(move || {
|
||||
let mut pollfd = libc::pollfd {
|
||||
fd,
|
||||
events: libc::POLLIN | libc::POLLERR | libc::POLLHUP,
|
||||
revents: 0,
|
||||
};
|
||||
loop {
|
||||
let result = unsafe { libc::poll(&mut pollfd, 1, -1) };
|
||||
if result > 0 {
|
||||
handle.complete(Ok(()));
|
||||
return;
|
||||
}
|
||||
if result == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() == io::ErrorKind::Interrupted {
|
||||
continue;
|
||||
}
|
||||
handle.complete(Err(error));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
future.await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn _duration_to_poll_timeout(duration: Duration) -> i32 {
|
||||
duration.as_millis().min(i32::MAX as u128) as i32
|
||||
}
|
||||
427
lib/runtime/src/sys/macos/fs.rs
Normal file
427
lib/runtime/src/sys/macos/fs.rs
Normal file
@@ -0,0 +1,427 @@
|
||||
//! macOS filesystem backend.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::CString;
|
||||
use std::future::poll_fn;
|
||||
use std::io;
|
||||
use std::os::fd::{FromRawFd, OwnedFd};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt, OpenOptionsExt};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use std::thread;
|
||||
|
||||
use crate::op::completion::completion_for_current_thread;
|
||||
use crate::op::fs::{FileType, FsOp, MetadataTarget, RawDirEntry, RawMetadata};
|
||||
use crate::platform::current::runtime::{ThreadHandle, current_thread_handle};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ExecutionPath {
|
||||
Offload,
|
||||
}
|
||||
|
||||
pub fn execution_path(_op: &FsOp) -> ExecutionPath {
|
||||
ExecutionPath::Offload
|
||||
}
|
||||
|
||||
pub async fn open(op: FsOp) -> io::Result<OwnedFd> {
|
||||
let FsOp::Open { path, options } = op else {
|
||||
unreachable!("open backend called with non-open op");
|
||||
};
|
||||
|
||||
offload(move || {
|
||||
let mut open = std::fs::OpenOptions::new();
|
||||
open.read(options.read)
|
||||
.write(options.write)
|
||||
.append(options.append)
|
||||
.truncate(options.truncate)
|
||||
.create(options.create)
|
||||
.create_new(options.create_new)
|
||||
.mode(0o666);
|
||||
let file = open.open(path)?;
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(std::os::fd::IntoRawFd::into_raw_fd(file)) })
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn read(op: FsOp) -> io::Result<Vec<u8>> {
|
||||
let FsOp::Read { fd, offset, len } = op else {
|
||||
unreachable!("read backend called with non-read op");
|
||||
};
|
||||
|
||||
offload(move || {
|
||||
let mut buffer = vec![0; len];
|
||||
let read = match offset {
|
||||
Some(offset) => unsafe {
|
||||
libc::pread(
|
||||
fd,
|
||||
buffer.as_mut_ptr().cast::<libc::c_void>(),
|
||||
len,
|
||||
offset as libc::off_t,
|
||||
)
|
||||
},
|
||||
None => unsafe { libc::read(fd, buffer.as_mut_ptr().cast::<libc::c_void>(), len) },
|
||||
};
|
||||
if read < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
buffer.truncate(read as usize);
|
||||
Ok(buffer)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn write(op: FsOp) -> io::Result<usize> {
|
||||
let FsOp::Write { fd, offset, data } = op else {
|
||||
unreachable!("write backend called with non-write op");
|
||||
};
|
||||
|
||||
offload(move || {
|
||||
let written = match offset {
|
||||
Some(offset) => unsafe {
|
||||
libc::pwrite(
|
||||
fd,
|
||||
data.as_ptr().cast::<libc::c_void>(),
|
||||
data.len(),
|
||||
offset as libc::off_t,
|
||||
)
|
||||
},
|
||||
None => unsafe { libc::write(fd, data.as_ptr().cast::<libc::c_void>(), data.len()) },
|
||||
};
|
||||
if written < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(written as usize)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn metadata(op: FsOp) -> io::Result<RawMetadata> {
|
||||
let FsOp::Metadata {
|
||||
target,
|
||||
follow_symlinks,
|
||||
} = op
|
||||
else {
|
||||
unreachable!("metadata backend called with non-metadata op");
|
||||
};
|
||||
|
||||
offload(move || {
|
||||
let metadata = match target {
|
||||
MetadataTarget::Path(path) => {
|
||||
if follow_symlinks {
|
||||
std::fs::metadata(path)
|
||||
} else {
|
||||
std::fs::symlink_metadata(path)
|
||||
}
|
||||
}
|
||||
MetadataTarget::File(fd) => {
|
||||
let mut stat = unsafe { std::mem::zeroed::<libc::stat>() };
|
||||
let result = unsafe { libc::fstat(fd, &mut stat) };
|
||||
if result < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
return Ok(raw_metadata_from_stat(&stat));
|
||||
}
|
||||
}?;
|
||||
Ok(raw_metadata_from_std(&metadata))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn sync_all(op: FsOp) -> io::Result<()> {
|
||||
let FsOp::SyncAll { fd } = op else {
|
||||
unreachable!("sync_all backend called with non-sync_all op");
|
||||
};
|
||||
|
||||
offload(move || cvt(unsafe { libc::fsync(fd) }).map(|_| ())).await
|
||||
}
|
||||
|
||||
pub async fn sync_data(op: FsOp) -> io::Result<()> {
|
||||
let FsOp::SyncData { fd } = op else {
|
||||
unreachable!("sync_data backend called with non-sync_data op");
|
||||
};
|
||||
|
||||
offload(move || cvt(unsafe { libc::fcntl(fd, libc::F_FULLFSYNC) }).map(|_| ())).await
|
||||
}
|
||||
|
||||
pub async fn set_len(op: FsOp) -> io::Result<()> {
|
||||
let FsOp::SetLen { fd, len } = op else {
|
||||
unreachable!("set_len backend called with non-set_len op");
|
||||
};
|
||||
|
||||
offload(move || cvt(unsafe { libc::ftruncate(fd, len as libc::off_t) }).map(|_| ())).await
|
||||
}
|
||||
|
||||
pub async fn try_clone(op: FsOp) -> io::Result<OwnedFd> {
|
||||
let FsOp::Duplicate { fd } = op else {
|
||||
unreachable!("try_clone backend called with non-duplicate op");
|
||||
};
|
||||
|
||||
offload(move || {
|
||||
let duplicated = cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) })?;
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(duplicated) })
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_dir(op: FsOp) -> io::Result<()> {
|
||||
let FsOp::CreateDir { path, mode } = op else {
|
||||
unreachable!("create_dir backend called with non-create_dir op");
|
||||
};
|
||||
|
||||
offload(move || {
|
||||
let c_path = path_to_c_string(path)?;
|
||||
cvt(unsafe { libc::mkdir(c_path.as_ptr(), mode as libc::mode_t) }).map(|_| ())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn remove_file(op: FsOp) -> io::Result<()> {
|
||||
let FsOp::RemoveFile { path } = op else {
|
||||
unreachable!("remove_file backend called with non-remove_file op");
|
||||
};
|
||||
|
||||
offload(move || std::fs::remove_file(path)).await
|
||||
}
|
||||
|
||||
pub async fn remove_dir(op: FsOp) -> io::Result<()> {
|
||||
let FsOp::RemoveDir { path } = op else {
|
||||
unreachable!("remove_dir backend called with non-remove_dir op");
|
||||
};
|
||||
|
||||
offload(move || std::fs::remove_dir(path)).await
|
||||
}
|
||||
|
||||
pub async fn rename(op: FsOp) -> io::Result<()> {
|
||||
let FsOp::Rename { from, to } = op else {
|
||||
unreachable!("rename backend called with non-rename op");
|
||||
};
|
||||
|
||||
offload(move || std::fs::rename(from, to)).await
|
||||
}
|
||||
|
||||
pub async fn close(op: FsOp) -> io::Result<()> {
|
||||
let FsOp::Close { fd } = op else {
|
||||
unreachable!("close backend called with non-close op");
|
||||
};
|
||||
|
||||
offload(move || cvt(unsafe { libc::close(fd) }).map(|_| ())).await
|
||||
}
|
||||
|
||||
pub fn read_dir(op: FsOp) -> io::Result<ReadDirStream> {
|
||||
let FsOp::ReadDir { path } = op else {
|
||||
unreachable!("read_dir backend called with non-read_dir op");
|
||||
};
|
||||
|
||||
ReadDirStream::new(path)
|
||||
}
|
||||
|
||||
pub struct ReadDirStream {
|
||||
state: Arc<ReadDirState>,
|
||||
}
|
||||
|
||||
impl ReadDirStream {
|
||||
fn new(path: PathBuf) -> io::Result<Self> {
|
||||
let state = Arc::new(ReadDirState::new(current_thread_handle()));
|
||||
let producer = Arc::clone(&state);
|
||||
|
||||
if let Err(error) = thread::Builder::new()
|
||||
.name("ruin-runtime-read-dir".into())
|
||||
.spawn(move || produce_dir_entries(path, producer))
|
||||
{
|
||||
state.release_pending();
|
||||
return Err(io::Error::other(error));
|
||||
}
|
||||
|
||||
Ok(Self { state })
|
||||
}
|
||||
|
||||
pub async fn next_entry(&mut self) -> io::Result<Option<RawDirEntry>> {
|
||||
poll_fn(|cx| self.state.poll_next(cx)).await
|
||||
}
|
||||
}
|
||||
|
||||
struct ReadDirState {
|
||||
owner: ThreadHandle,
|
||||
queue: Mutex<VecDeque<io::Result<RawDirEntry>>>,
|
||||
done: AtomicBool,
|
||||
pending: AtomicBool,
|
||||
wake_queued: AtomicBool,
|
||||
waker: Mutex<Option<Waker>>,
|
||||
}
|
||||
|
||||
impl ReadDirState {
|
||||
fn new(owner: ThreadHandle) -> Self {
|
||||
owner.begin_async_operation();
|
||||
Self {
|
||||
owner,
|
||||
queue: Mutex::new(VecDeque::new()),
|
||||
done: AtomicBool::new(false),
|
||||
pending: AtomicBool::new(true),
|
||||
wake_queued: AtomicBool::new(false),
|
||||
waker: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(self: &Arc<Self>, entry: io::Result<RawDirEntry>) {
|
||||
self.queue.lock().unwrap().push_back(entry);
|
||||
self.notify();
|
||||
}
|
||||
|
||||
fn finish(self: &Arc<Self>) {
|
||||
self.done.store(true, Ordering::Release);
|
||||
self.release_pending();
|
||||
self.notify();
|
||||
}
|
||||
|
||||
fn release_pending(&self) {
|
||||
if self.pending.swap(false, Ordering::AcqRel) {
|
||||
self.owner.finish_async_operation();
|
||||
}
|
||||
}
|
||||
|
||||
fn notify(self: &Arc<Self>) {
|
||||
if self.wake_queued.swap(true, Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
|
||||
let state = Arc::clone(self);
|
||||
if !self.owner.queue_task(move || {
|
||||
state.wake_queued.store(false, Ordering::Release);
|
||||
if let Some(waker) = state.waker.lock().unwrap().take() {
|
||||
waker.wake();
|
||||
}
|
||||
}) {
|
||||
self.wake_queued.store(false, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next(&self, cx: &mut Context<'_>) -> Poll<io::Result<Option<RawDirEntry>>> {
|
||||
if let Some(entry) = self.queue.lock().unwrap().pop_front() {
|
||||
return Poll::Ready(entry.map(Some));
|
||||
}
|
||||
|
||||
if self.done.load(Ordering::Acquire) {
|
||||
return Poll::Ready(Ok(None));
|
||||
}
|
||||
|
||||
*self.waker.lock().unwrap() = Some(cx.waker().clone());
|
||||
|
||||
if let Some(entry) = self.queue.lock().unwrap().pop_front() {
|
||||
let _ = self.waker.lock().unwrap().take();
|
||||
return Poll::Ready(entry.map(Some));
|
||||
}
|
||||
|
||||
if self.done.load(Ordering::Acquire) {
|
||||
let _ = self.waker.lock().unwrap().take();
|
||||
return Poll::Ready(Ok(None));
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ReadDirStream {
|
||||
fn drop(&mut self) {
|
||||
self.state.release_pending();
|
||||
}
|
||||
}
|
||||
|
||||
fn produce_dir_entries(path: PathBuf, state: Arc<ReadDirState>) {
|
||||
match std::fs::read_dir(path) {
|
||||
Ok(entries) => {
|
||||
for entry in entries {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let file_name = entry.file_name();
|
||||
state.push(Ok(RawDirEntry {
|
||||
path: entry.path(),
|
||||
file_name,
|
||||
}));
|
||||
}
|
||||
Err(error) => state.push(Err(error)),
|
||||
}
|
||||
}
|
||||
state.finish();
|
||||
}
|
||||
Err(error) => {
|
||||
state.push(Err(error));
|
||||
state.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn offload<T: Send + 'static>(
|
||||
work: impl FnOnce() -> io::Result<T> + Send + 'static,
|
||||
) -> io::Result<T> {
|
||||
let (future, handle) = completion_for_current_thread::<io::Result<T>>();
|
||||
thread::spawn(move || {
|
||||
handle.complete(work());
|
||||
});
|
||||
future.await
|
||||
}
|
||||
|
||||
fn path_to_c_string(path: PathBuf) -> io::Result<CString> {
|
||||
CString::new(path.as_os_str().as_bytes()).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"path contains interior NUL bytes",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn raw_metadata_from_std(metadata: &std::fs::Metadata) -> RawMetadata {
|
||||
let file_type = metadata.file_type();
|
||||
let kind = if file_type.is_file() {
|
||||
FileType::File
|
||||
} else if file_type.is_dir() {
|
||||
FileType::Directory
|
||||
} else if file_type.is_symlink() {
|
||||
FileType::Symlink
|
||||
} else if file_type.is_block_device() {
|
||||
FileType::BlockDevice
|
||||
} else if file_type.is_char_device() {
|
||||
FileType::CharacterDevice
|
||||
} else if file_type.is_fifo() {
|
||||
FileType::Fifo
|
||||
} else if file_type.is_socket() {
|
||||
FileType::Socket
|
||||
} else {
|
||||
FileType::Unknown
|
||||
};
|
||||
|
||||
RawMetadata {
|
||||
file_type: kind,
|
||||
mode: (metadata.mode() & 0o7777) as u16,
|
||||
len: metadata.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_metadata_from_stat(stat: &libc::stat) -> RawMetadata {
|
||||
let kind = match stat.st_mode & libc::S_IFMT {
|
||||
libc::S_IFREG => FileType::File,
|
||||
libc::S_IFDIR => FileType::Directory,
|
||||
libc::S_IFLNK => FileType::Symlink,
|
||||
libc::S_IFBLK => FileType::BlockDevice,
|
||||
libc::S_IFCHR => FileType::CharacterDevice,
|
||||
libc::S_IFIFO => FileType::Fifo,
|
||||
libc::S_IFSOCK => FileType::Socket,
|
||||
_ => FileType::Unknown,
|
||||
};
|
||||
|
||||
RawMetadata {
|
||||
file_type: kind,
|
||||
mode: stat.st_mode & 0o7777,
|
||||
len: stat.st_size as u64,
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt(value: libc::c_int) -> io::Result<libc::c_int> {
|
||||
if value < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
6
lib/runtime/src/sys/macos/mod.rs
Normal file
6
lib/runtime/src/sys/macos/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
//! macOS backend modules.
|
||||
|
||||
pub mod channel;
|
||||
pub mod fd;
|
||||
pub mod fs;
|
||||
pub mod net;
|
||||
779
lib/runtime/src/sys/macos/net.rs
Normal file
779
lib/runtime/src/sys/macos/net.rs
Normal file
@@ -0,0 +1,779 @@
|
||||
//! macOS networking backend.
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::net::{
|
||||
Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs,
|
||||
};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::pin::Pin;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::op::completion::completion_for_current_thread;
|
||||
use crate::op::net::{AcceptedSocket, NetOp, ReceivedDatagram};
|
||||
|
||||
const DEFAULT_LISTENER_BACKLOG: i32 = 1024;
|
||||
|
||||
type RecvFuture = Pin<Box<dyn Future<Output = io::Result<Vec<u8>>> + 'static>>;
|
||||
type SendFuture = Pin<Box<dyn Future<Output = io::Result<usize>> + 'static>>;
|
||||
type ShutdownFuture = Pin<Box<dyn Future<Output = io::Result<()>> + 'static>>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ExecutionPath {
|
||||
Offload,
|
||||
}
|
||||
|
||||
pub fn execution_path(_op: &NetOp) -> ExecutionPath {
|
||||
ExecutionPath::Offload
|
||||
}
|
||||
|
||||
pub async fn resolve_addrs<A>(addr: A) -> io::Result<Vec<SocketAddr>>
|
||||
where
|
||||
A: ToSocketAddrs + Send + 'static,
|
||||
{
|
||||
offload(move || {
|
||||
let addrs = addr.to_socket_addrs()?.collect::<Vec<_>>();
|
||||
if addrs.is_empty() {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"address resolved to no socket addresses",
|
||||
))
|
||||
} else {
|
||||
Ok(addrs)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn socket(op: NetOp) -> io::Result<OwnedFd> {
|
||||
let NetOp::Socket {
|
||||
domain,
|
||||
socket_type,
|
||||
protocol,
|
||||
flags,
|
||||
} = op
|
||||
else {
|
||||
unreachable!("socket backend called with non-socket op");
|
||||
};
|
||||
|
||||
offload(move || socket_sync(domain, socket_type, protocol, flags)).await
|
||||
}
|
||||
|
||||
pub async fn connect(op: NetOp) -> io::Result<()> {
|
||||
let NetOp::Connect { fd, addr } = op else {
|
||||
unreachable!("connect backend called with non-connect op");
|
||||
};
|
||||
|
||||
offload(move || connect_sync(fd, RawSocketAddr::from_socket_addr(addr))).await
|
||||
}
|
||||
|
||||
pub async fn bind(op: NetOp) -> io::Result<()> {
|
||||
let NetOp::Bind { fd, addr } = op else {
|
||||
unreachable!("bind backend called with non-bind op");
|
||||
};
|
||||
|
||||
offload(move || bind_sync(fd, RawSocketAddr::from_socket_addr(addr))).await
|
||||
}
|
||||
|
||||
pub async fn listen(op: NetOp) -> io::Result<()> {
|
||||
let NetOp::Listen { fd, backlog } = op else {
|
||||
unreachable!("listen backend called with non-listen op");
|
||||
};
|
||||
|
||||
offload(move || listen_sync(fd, backlog)).await
|
||||
}
|
||||
|
||||
pub async fn accept(op: NetOp) -> io::Result<AcceptedSocket> {
|
||||
let NetOp::Accept { fd } = op else {
|
||||
unreachable!("accept backend called with non-accept op");
|
||||
};
|
||||
|
||||
offload(move || accept_sync(fd)).await
|
||||
}
|
||||
|
||||
pub async fn send(op: NetOp) -> io::Result<usize> {
|
||||
let NetOp::Send { fd, data, flags } = op else {
|
||||
unreachable!("send backend called with non-send op");
|
||||
};
|
||||
|
||||
offload(move || send_sync(fd, data, flags)).await
|
||||
}
|
||||
|
||||
pub async fn send_to(op: NetOp) -> io::Result<usize> {
|
||||
let NetOp::SendTo {
|
||||
fd,
|
||||
target,
|
||||
data,
|
||||
flags,
|
||||
} = op
|
||||
else {
|
||||
unreachable!("send_to backend called with non-send_to op");
|
||||
};
|
||||
|
||||
offload(move || send_to_sync(fd, target, data, flags)).await
|
||||
}
|
||||
|
||||
pub async fn recv(op: NetOp) -> io::Result<Vec<u8>> {
|
||||
let NetOp::Recv { fd, len, flags } = op else {
|
||||
unreachable!("recv backend called with non-recv op");
|
||||
};
|
||||
|
||||
offload(move || recv_sync(fd, len, flags)).await
|
||||
}
|
||||
|
||||
pub async fn recv_from(op: NetOp) -> io::Result<ReceivedDatagram> {
|
||||
let NetOp::RecvFrom { fd, len, flags } = op else {
|
||||
unreachable!("recv_from backend called with non-recv_from op");
|
||||
};
|
||||
|
||||
offload(move || recv_from_sync(fd, len, flags)).await
|
||||
}
|
||||
|
||||
pub async fn shutdown(op: NetOp) -> io::Result<()> {
|
||||
let NetOp::Shutdown { fd, how } = op else {
|
||||
unreachable!("shutdown backend called with non-shutdown op");
|
||||
};
|
||||
|
||||
offload(move || shutdown_sync(fd, how)).await
|
||||
}
|
||||
|
||||
pub async fn close(op: NetOp) -> io::Result<()> {
|
||||
let NetOp::Close { fd } = op else {
|
||||
unreachable!("close backend called with non-close op");
|
||||
};
|
||||
|
||||
offload(move || close_sync(fd)).await
|
||||
}
|
||||
|
||||
pub async fn connect_stream(addr: SocketAddr) -> io::Result<OwnedFd> {
|
||||
match connect_stream_inner(addr).await {
|
||||
Err(error) if should_try_ipv4_loopback(addr, &error) => {
|
||||
connect_stream_inner(localhost_v4(addr)).await
|
||||
}
|
||||
result => result,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bind_listener(addr: SocketAddr, backlog: Option<i32>) -> io::Result<OwnedFd> {
|
||||
match bind_listener_inner(addr, backlog).await {
|
||||
Err(error) if should_try_ipv4_loopback(addr, &error) => {
|
||||
bind_listener_inner(localhost_v4(addr), backlog).await
|
||||
}
|
||||
result => result,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bind_datagram(addr: SocketAddr) -> io::Result<OwnedFd> {
|
||||
match bind_datagram_inner(addr).await {
|
||||
Err(error) if should_try_ipv4_loopback(addr, &error) => {
|
||||
bind_datagram_inner(localhost_v4(addr)).await
|
||||
}
|
||||
result => result,
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect_stream_inner(addr: SocketAddr) -> io::Result<OwnedFd> {
|
||||
let stream = socket(NetOp::Socket {
|
||||
domain: socket_domain(addr),
|
||||
socket_type: libc::SOCK_STREAM,
|
||||
protocol: 0,
|
||||
flags: 0,
|
||||
})
|
||||
.await?;
|
||||
|
||||
connect(NetOp::Connect {
|
||||
fd: stream.as_raw_fd(),
|
||||
addr,
|
||||
})
|
||||
.await?;
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
async fn bind_listener_inner(addr: SocketAddr, backlog: Option<i32>) -> io::Result<OwnedFd> {
|
||||
let listener = socket(NetOp::Socket {
|
||||
domain: socket_domain(addr),
|
||||
socket_type: libc::SOCK_STREAM,
|
||||
protocol: 0,
|
||||
flags: 0,
|
||||
})
|
||||
.await?;
|
||||
|
||||
set_reuse_addr(listener.as_raw_fd(), true)?;
|
||||
|
||||
bind(NetOp::Bind {
|
||||
fd: listener.as_raw_fd(),
|
||||
addr,
|
||||
})
|
||||
.await?;
|
||||
listen(NetOp::Listen {
|
||||
fd: listener.as_raw_fd(),
|
||||
backlog: backlog.unwrap_or(DEFAULT_LISTENER_BACKLOG),
|
||||
})
|
||||
.await?;
|
||||
Ok(listener)
|
||||
}
|
||||
|
||||
async fn bind_datagram_inner(addr: SocketAddr) -> io::Result<OwnedFd> {
|
||||
let socket = socket(NetOp::Socket {
|
||||
domain: socket_domain(addr),
|
||||
socket_type: libc::SOCK_DGRAM,
|
||||
protocol: 0,
|
||||
flags: 0,
|
||||
})
|
||||
.await?;
|
||||
|
||||
bind(NetOp::Bind {
|
||||
fd: socket.as_raw_fd(),
|
||||
addr,
|
||||
})
|
||||
.await?;
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
pub async fn duplicate(fd: RawFd) -> io::Result<OwnedFd> {
|
||||
offload(move || {
|
||||
let duplicated = cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) })?;
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(duplicated) })
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn recv_timeout(
|
||||
fd: RawFd,
|
||||
len: usize,
|
||||
flags: i32,
|
||||
timeout: Duration,
|
||||
) -> io::Result<Vec<u8>> {
|
||||
offload(move || {
|
||||
wait_for_fd(fd, libc::POLLIN, timeout)?;
|
||||
recv_sync(fd, len, flags)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_timeout(
|
||||
fd: RawFd,
|
||||
data: Vec<u8>,
|
||||
flags: i32,
|
||||
timeout: Duration,
|
||||
) -> io::Result<usize> {
|
||||
offload(move || {
|
||||
wait_for_fd(fd, libc::POLLOUT, timeout)?;
|
||||
send_sync(fd, data, flags)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn recv_from_timeout(
|
||||
fd: RawFd,
|
||||
len: usize,
|
||||
flags: i32,
|
||||
timeout: Duration,
|
||||
) -> io::Result<ReceivedDatagram> {
|
||||
offload(move || {
|
||||
wait_for_fd(fd, libc::POLLIN, timeout)?;
|
||||
recv_from_sync(fd, len, flags)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_to_timeout(
|
||||
fd: RawFd,
|
||||
data: Vec<u8>,
|
||||
target: SocketAddr,
|
||||
flags: i32,
|
||||
timeout: Duration,
|
||||
) -> io::Result<usize> {
|
||||
offload(move || {
|
||||
wait_for_fd(fd, libc::POLLOUT, timeout)?;
|
||||
send_to_sync(fd, target, data, flags)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn connect_stream_timeout(addr: SocketAddr, timeout: Duration) -> io::Result<OwnedFd> {
|
||||
offload(move || connect_stream_timeout_sync(addr, timeout)).await
|
||||
}
|
||||
|
||||
pub fn local_addr(fd: RawFd) -> io::Result<SocketAddr> {
|
||||
socket_addr_with(libc::getsockname, fd)
|
||||
}
|
||||
|
||||
pub fn peer_addr(fd: RawFd) -> io::Result<SocketAddr> {
|
||||
socket_addr_with(libc::getpeername, fd)
|
||||
}
|
||||
|
||||
pub fn nodelay(fd: RawFd) -> io::Result<bool> {
|
||||
let mut value = 0;
|
||||
let mut len = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
|
||||
cvt(unsafe {
|
||||
libc::getsockopt(
|
||||
fd,
|
||||
libc::IPPROTO_TCP,
|
||||
libc::TCP_NODELAY,
|
||||
&mut value as *mut libc::c_int as *mut c_void,
|
||||
&mut len,
|
||||
)
|
||||
})?;
|
||||
Ok(value != 0)
|
||||
}
|
||||
|
||||
pub fn broadcast(fd: RawFd) -> io::Result<bool> {
|
||||
getsockopt_int(fd, libc::SOL_SOCKET, libc::SO_BROADCAST).map(|value| value != 0)
|
||||
}
|
||||
|
||||
pub fn set_broadcast(fd: RawFd, enabled: bool) -> io::Result<()> {
|
||||
setsockopt_int(fd, libc::SOL_SOCKET, libc::SO_BROADCAST, enabled.into())
|
||||
}
|
||||
|
||||
pub fn ttl(fd: RawFd) -> io::Result<u32> {
|
||||
match socket_family(fd)? {
|
||||
libc::AF_INET => {
|
||||
getsockopt_int(fd, libc::IPPROTO_IP, libc::IP_TTL).map(|value| value as u32)
|
||||
}
|
||||
libc::AF_INET6 => getsockopt_int(fd, libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS)
|
||||
.map(|value| value as u32),
|
||||
family => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("unsupported socket family {family} for TTL"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ttl(fd: RawFd, ttl: u32) -> io::Result<()> {
|
||||
let ttl = i32::try_from(ttl)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "TTL exceeds i32 range"))?;
|
||||
match socket_family(fd)? {
|
||||
libc::AF_INET => setsockopt_int(fd, libc::IPPROTO_IP, libc::IP_TTL, ttl),
|
||||
libc::AF_INET6 => setsockopt_int(fd, libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS, ttl),
|
||||
family => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("unsupported socket family {family} for TTL"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_nodelay(fd: RawFd, enabled: bool) -> io::Result<()> {
|
||||
let value: libc::c_int = enabled.into();
|
||||
cvt(unsafe {
|
||||
libc::setsockopt(
|
||||
fd,
|
||||
libc::IPPROTO_TCP,
|
||||
libc::TCP_NODELAY,
|
||||
&value as *const libc::c_int as *const c_void,
|
||||
std::mem::size_of_val(&value) as libc::socklen_t,
|
||||
)
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn recv_future(fd: RawFd, len: usize) -> RecvFuture {
|
||||
Box::pin(recv(NetOp::Recv { fd, len, flags: 0 }))
|
||||
}
|
||||
|
||||
pub fn send_future(fd: RawFd, data: Vec<u8>) -> SendFuture {
|
||||
Box::pin(send(NetOp::Send { fd, data, flags: 0 }))
|
||||
}
|
||||
|
||||
pub fn shutdown_future(fd: RawFd, how: Shutdown) -> ShutdownFuture {
|
||||
Box::pin(shutdown(NetOp::Shutdown { fd, how }))
|
||||
}
|
||||
|
||||
async fn offload<T: Send + 'static>(
|
||||
work: impl FnOnce() -> io::Result<T> + Send + 'static,
|
||||
) -> io::Result<T> {
|
||||
let (future, handle) = completion_for_current_thread::<io::Result<T>>();
|
||||
let handle_for_thread = handle.clone();
|
||||
if let Err(error) = thread::Builder::new()
|
||||
.name("ruin-runtime-net-offload".into())
|
||||
.spawn(move || handle_for_thread.complete(work()))
|
||||
{
|
||||
handle.complete(Err(io::Error::other(error)));
|
||||
}
|
||||
future.await
|
||||
}
|
||||
|
||||
fn socket_domain(addr: SocketAddr) -> i32 {
|
||||
match addr {
|
||||
SocketAddr::V4(_) => libc::AF_INET,
|
||||
SocketAddr::V6(_) => libc::AF_INET6,
|
||||
}
|
||||
}
|
||||
|
||||
fn shutdown_how(how: Shutdown) -> i32 {
|
||||
match how {
|
||||
Shutdown::Read => libc::SHUT_RD,
|
||||
Shutdown::Write => libc::SHUT_WR,
|
||||
Shutdown::Both => libc::SHUT_RDWR,
|
||||
}
|
||||
}
|
||||
|
||||
fn socket_addr_with(
|
||||
op: unsafe extern "C" fn(RawFd, *mut libc::sockaddr, *mut libc::socklen_t) -> libc::c_int,
|
||||
fd: RawFd,
|
||||
) -> io::Result<SocketAddr> {
|
||||
let mut storage = MaybeUninit::<libc::sockaddr_storage>::zeroed();
|
||||
let mut len = std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
|
||||
cvt(unsafe { op(fd, storage.as_mut_ptr().cast::<libc::sockaddr>(), &mut len) })?;
|
||||
let storage = unsafe { storage.assume_init() };
|
||||
socket_addr_from_storage(&storage, len)
|
||||
}
|
||||
|
||||
fn set_reuse_addr(fd: RawFd, enabled: bool) -> io::Result<()> {
|
||||
setsockopt_int(fd, libc::SOL_SOCKET, libc::SO_REUSEADDR, enabled.into())
|
||||
}
|
||||
|
||||
fn socket_family(fd: RawFd) -> io::Result<i32> {
|
||||
let mut storage = MaybeUninit::<libc::sockaddr_storage>::zeroed();
|
||||
let mut len = std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
|
||||
cvt(unsafe { libc::getsockname(fd, storage.as_mut_ptr().cast::<libc::sockaddr>(), &mut len) })?;
|
||||
let storage = unsafe { storage.assume_init() };
|
||||
Ok(storage.ss_family as i32)
|
||||
}
|
||||
|
||||
fn getsockopt_int(fd: RawFd, level: i32, name: i32) -> io::Result<i32> {
|
||||
let mut value = 0;
|
||||
let mut len = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
|
||||
cvt(unsafe {
|
||||
libc::getsockopt(
|
||||
fd,
|
||||
level,
|
||||
name,
|
||||
&mut value as *mut libc::c_int as *mut c_void,
|
||||
&mut len,
|
||||
)
|
||||
})?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn setsockopt_int(fd: RawFd, level: i32, name: i32, value: i32) -> io::Result<()> {
|
||||
cvt(unsafe {
|
||||
libc::setsockopt(
|
||||
fd,
|
||||
level,
|
||||
name,
|
||||
&value as *const libc::c_int as *const c_void,
|
||||
std::mem::size_of_val(&value) as libc::socklen_t,
|
||||
)
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn socket_addr_from_storage(
|
||||
storage: &libc::sockaddr_storage,
|
||||
len: libc::socklen_t,
|
||||
) -> io::Result<SocketAddr> {
|
||||
match storage.ss_family as i32 {
|
||||
libc::AF_INET => {
|
||||
if len < std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"sockaddr_in length is truncated",
|
||||
));
|
||||
}
|
||||
let addr = unsafe { *(storage as *const _ as *const libc::sockaddr_in) };
|
||||
Ok(SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr)),
|
||||
u16::from_be(addr.sin_port),
|
||||
)))
|
||||
}
|
||||
libc::AF_INET6 => {
|
||||
if len < std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"sockaddr_in6 length is truncated",
|
||||
));
|
||||
}
|
||||
let addr = unsafe { *(storage as *const _ as *const libc::sockaddr_in6) };
|
||||
Ok(SocketAddr::V6(SocketAddrV6::new(
|
||||
Ipv6Addr::from(addr.sin6_addr.s6_addr),
|
||||
u16::from_be(addr.sin6_port),
|
||||
addr.sin6_flowinfo,
|
||||
addr.sin6_scope_id,
|
||||
)))
|
||||
}
|
||||
family => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("unsupported socket family {family}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct RawSocketAddr {
|
||||
storage: libc::sockaddr_storage,
|
||||
len: libc::socklen_t,
|
||||
}
|
||||
|
||||
impl RawSocketAddr {
|
||||
fn from_socket_addr(addr: SocketAddr) -> Self {
|
||||
match addr {
|
||||
SocketAddr::V4(addr) => {
|
||||
let sockaddr = libc::sockaddr_in {
|
||||
sin_len: std::mem::size_of::<libc::sockaddr_in>() as u8,
|
||||
sin_family: libc::AF_INET as libc::sa_family_t,
|
||||
sin_port: addr.port().to_be(),
|
||||
sin_addr: libc::in_addr {
|
||||
s_addr: u32::from_ne_bytes(addr.ip().octets()),
|
||||
},
|
||||
sin_zero: [0; 8],
|
||||
};
|
||||
let mut storage =
|
||||
unsafe { MaybeUninit::<libc::sockaddr_storage>::zeroed().assume_init() };
|
||||
unsafe {
|
||||
std::ptr::write(
|
||||
&mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_in,
|
||||
sockaddr,
|
||||
);
|
||||
}
|
||||
Self {
|
||||
storage,
|
||||
len: std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
|
||||
}
|
||||
}
|
||||
SocketAddr::V6(addr) => {
|
||||
let sockaddr = libc::sockaddr_in6 {
|
||||
sin6_len: std::mem::size_of::<libc::sockaddr_in6>() as u8,
|
||||
sin6_family: libc::AF_INET6 as libc::sa_family_t,
|
||||
sin6_port: addr.port().to_be(),
|
||||
sin6_flowinfo: addr.flowinfo(),
|
||||
sin6_addr: libc::in6_addr {
|
||||
s6_addr: addr.ip().octets(),
|
||||
},
|
||||
sin6_scope_id: addr.scope_id(),
|
||||
};
|
||||
let mut storage =
|
||||
unsafe { MaybeUninit::<libc::sockaddr_storage>::zeroed().assume_init() };
|
||||
unsafe {
|
||||
std::ptr::write(
|
||||
&mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_in6,
|
||||
sockaddr,
|
||||
);
|
||||
}
|
||||
Self {
|
||||
storage,
|
||||
len: std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const libc::sockaddr {
|
||||
&self.storage as *const libc::sockaddr_storage as *const libc::sockaddr
|
||||
}
|
||||
|
||||
fn len(&self) -> libc::socklen_t {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
|
||||
fn socket_sync(domain: i32, socket_type: i32, protocol: i32, _flags: u32) -> io::Result<OwnedFd> {
|
||||
let fd = cvt(unsafe { libc::socket(domain, socket_type, protocol) })?;
|
||||
set_cloexec(fd)?;
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
|
||||
}
|
||||
|
||||
fn connect_sync(fd: RawFd, addr: RawSocketAddr) -> io::Result<()> {
|
||||
cvt(unsafe { libc::connect(fd, addr.as_ptr(), addr.len()) }).map(|_| ())
|
||||
}
|
||||
|
||||
fn bind_sync(fd: RawFd, addr: RawSocketAddr) -> io::Result<()> {
|
||||
cvt(unsafe { libc::bind(fd, addr.as_ptr(), addr.len()) }).map(|_| ())
|
||||
}
|
||||
|
||||
fn listen_sync(fd: RawFd, backlog: i32) -> io::Result<()> {
|
||||
cvt(unsafe { libc::listen(fd, backlog) }).map(|_| ())
|
||||
}
|
||||
|
||||
fn accept_sync(fd: RawFd) -> io::Result<AcceptedSocket> {
|
||||
let mut storage = MaybeUninit::<libc::sockaddr_storage>::zeroed();
|
||||
let mut len = std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
|
||||
let accepted_fd =
|
||||
cvt(unsafe { libc::accept(fd, storage.as_mut_ptr().cast::<libc::sockaddr>(), &mut len) })?;
|
||||
set_cloexec(accepted_fd)?;
|
||||
let storage = unsafe { storage.assume_init() };
|
||||
let peer_addr = socket_addr_from_storage(&storage, len)?;
|
||||
Ok(AcceptedSocket {
|
||||
fd: accepted_fd,
|
||||
peer_addr,
|
||||
})
|
||||
}
|
||||
|
||||
fn send_sync(fd: RawFd, data: Vec<u8>, flags: i32) -> io::Result<usize> {
|
||||
let written = unsafe { libc::send(fd, data.as_ptr().cast::<c_void>(), data.len(), flags) };
|
||||
cvt_long(written).map(|written| written as usize)
|
||||
}
|
||||
|
||||
fn send_to_sync(fd: RawFd, target: SocketAddr, data: Vec<u8>, flags: i32) -> io::Result<usize> {
|
||||
let addr = RawSocketAddr::from_socket_addr(target);
|
||||
let written = unsafe {
|
||||
libc::sendto(
|
||||
fd,
|
||||
data.as_ptr().cast::<c_void>(),
|
||||
data.len(),
|
||||
flags,
|
||||
addr.as_ptr(),
|
||||
addr.len(),
|
||||
)
|
||||
};
|
||||
cvt_long(written).map(|written| written as usize)
|
||||
}
|
||||
|
||||
fn recv_sync(fd: RawFd, len: usize, flags: i32) -> io::Result<Vec<u8>> {
|
||||
let mut data = vec![0u8; len];
|
||||
let read = unsafe { libc::recv(fd, data.as_mut_ptr().cast::<c_void>(), len, flags) };
|
||||
let read = cvt_long(read)? as usize;
|
||||
data.truncate(read);
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn recv_from_sync(fd: RawFd, len: usize, flags: i32) -> io::Result<ReceivedDatagram> {
|
||||
let mut data = vec![0u8; len];
|
||||
let mut storage = MaybeUninit::<libc::sockaddr_storage>::zeroed();
|
||||
let mut addr_len = std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
|
||||
let read = unsafe {
|
||||
libc::recvfrom(
|
||||
fd,
|
||||
data.as_mut_ptr().cast::<c_void>(),
|
||||
len,
|
||||
flags,
|
||||
storage.as_mut_ptr().cast::<libc::sockaddr>(),
|
||||
&mut addr_len,
|
||||
)
|
||||
};
|
||||
let read = cvt_long(read)? as usize;
|
||||
data.truncate(read);
|
||||
let storage = unsafe { storage.assume_init() };
|
||||
let peer_addr = socket_addr_from_storage(&storage, addr_len)?;
|
||||
Ok(ReceivedDatagram { data, peer_addr })
|
||||
}
|
||||
|
||||
fn shutdown_sync(fd: RawFd, how: Shutdown) -> io::Result<()> {
|
||||
cvt(unsafe { libc::shutdown(fd, shutdown_how(how)) }).map(|_| ())
|
||||
}
|
||||
|
||||
fn close_sync(fd: RawFd) -> io::Result<()> {
|
||||
cvt(unsafe { libc::close(fd) }).map(|_| ())
|
||||
}
|
||||
|
||||
fn connect_stream_timeout_sync(addr: SocketAddr, timeout: Duration) -> io::Result<OwnedFd> {
|
||||
let fd = cvt(unsafe { libc::socket(socket_domain(addr), libc::SOCK_STREAM, 0) })?;
|
||||
set_cloexec(fd)?;
|
||||
set_nonblocking(fd)?;
|
||||
|
||||
let raw = RawSocketAddr::from_socket_addr(addr);
|
||||
let connect_result = unsafe { libc::connect(fd, raw.as_ptr(), raw.len()) };
|
||||
if connect_result < 0 {
|
||||
let error = io::Error::last_os_error();
|
||||
if error.raw_os_error() != Some(libc::EINPROGRESS) {
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
let wait = wait_for_fd(fd, libc::POLLOUT, timeout);
|
||||
if let Err(error) = wait {
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let mut so_error: libc::c_int = 0;
|
||||
let mut len = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
|
||||
if unsafe {
|
||||
libc::getsockopt(
|
||||
fd,
|
||||
libc::SOL_SOCKET,
|
||||
libc::SO_ERROR,
|
||||
&mut so_error as *mut libc::c_int as *mut c_void,
|
||||
&mut len,
|
||||
)
|
||||
} < 0
|
||||
{
|
||||
let error = io::Error::last_os_error();
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
if so_error != 0 {
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
return Err(io::Error::from_raw_os_error(so_error));
|
||||
}
|
||||
|
||||
clear_nonblocking(fd)?;
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
|
||||
}
|
||||
|
||||
fn wait_for_fd(fd: RawFd, events: i16, timeout: Duration) -> io::Result<()> {
|
||||
let timeout_ms = timeout.as_millis().min(i32::MAX as u128) as i32;
|
||||
let mut pfd = libc::pollfd {
|
||||
fd,
|
||||
events,
|
||||
revents: 0,
|
||||
};
|
||||
loop {
|
||||
let result = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
|
||||
if result > 0 {
|
||||
return Ok(());
|
||||
}
|
||||
if result == 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::TimedOut,
|
||||
"operation timed out",
|
||||
));
|
||||
}
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() == io::ErrorKind::Interrupted {
|
||||
continue;
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cloexec(fd: RawFd) -> io::Result<()> {
|
||||
let flags = cvt(unsafe { libc::fcntl(fd, libc::F_GETFD) })?;
|
||||
cvt(unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_nonblocking(fd: RawFd) -> io::Result<()> {
|
||||
let flags = cvt(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
|
||||
cvt(unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_nonblocking(fd: RawFd) -> io::Result<()> {
|
||||
let flags = cvt(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
|
||||
cvt(unsafe { libc::fcntl(fd, libc::F_SETFL, flags & !libc::O_NONBLOCK) })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn should_try_ipv4_loopback(addr: SocketAddr, error: &io::Error) -> bool {
|
||||
matches!(addr, SocketAddr::V6(v6) if v6.ip().is_loopback())
|
||||
&& matches!(
|
||||
error.raw_os_error(),
|
||||
Some(libc::EADDRNOTAVAIL | libc::EAFNOSUPPORT | libc::ENETUNREACH)
|
||||
)
|
||||
}
|
||||
|
||||
fn localhost_v4(addr: SocketAddr) -> SocketAddr {
|
||||
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, addr.port()))
|
||||
}
|
||||
|
||||
fn cvt(value: libc::c_int) -> io::Result<libc::c_int> {
|
||||
if value < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt_long(value: libc::ssize_t) -> io::Result<libc::ssize_t> {
|
||||
if value < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,10 @@
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub mod linux;
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
pub mod macos;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub use linux as current;
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
pub use macos as current;
|
||||
|
||||
@@ -67,7 +67,9 @@ fn bench_scroll_list(c: &mut Criterion) {
|
||||
let viewport_height = 640.0;
|
||||
let n = 500;
|
||||
|
||||
let mut scroll_box = Element::scroll_box(0.0).width(400.0).height(viewport_height);
|
||||
let mut scroll_box = Element::scroll_box(0.0)
|
||||
.width(400.0)
|
||||
.height(viewport_height);
|
||||
for i in 0..n {
|
||||
scroll_box = scroll_box.child(
|
||||
Element::new()
|
||||
@@ -90,5 +92,10 @@ fn bench_scroll_list(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_static_tree, bench_single_change, bench_scroll_list);
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_static_tree,
|
||||
bench_single_change,
|
||||
bench_scroll_list
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
||||
@@ -107,6 +107,7 @@ fn log_platform_event(event: &PlatformEvent) {
|
||||
"clipboard text received"
|
||||
);
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
PlatformEvent::PrimarySelectionText { window_id, text } => {
|
||||
tracing::debug!(
|
||||
event = "primary_selection_text",
|
||||
|
||||
@@ -128,7 +128,12 @@ impl LayoutNode {
|
||||
/// Return a copy of this node (and all descendants) with all positions shifted by `offset`.
|
||||
pub fn translated(self, offset: Point) -> Self {
|
||||
fn translate_rect(r: Rect, o: Point) -> Rect {
|
||||
Rect::new(r.origin.x + o.x, r.origin.y + o.y, r.size.width, r.size.height)
|
||||
Rect::new(
|
||||
r.origin.x + o.x,
|
||||
r.origin.y + o.y,
|
||||
r.size.width,
|
||||
r.size.height,
|
||||
)
|
||||
}
|
||||
let rect = translate_rect(self.rect, offset);
|
||||
let clip_rect = self.clip_rect.map(|r| translate_rect(r, offset));
|
||||
@@ -143,7 +148,11 @@ impl LayoutNode {
|
||||
rect: translate_rect(img.rect, offset),
|
||||
..img
|
||||
});
|
||||
let children = self.children.into_iter().map(|c| c.translated(offset)).collect();
|
||||
let children = self
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|c| c.translated(offset))
|
||||
.collect();
|
||||
LayoutNode {
|
||||
rect,
|
||||
clip_rect,
|
||||
@@ -241,6 +250,7 @@ pub fn layout_snapshot_with_cache(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_element(
|
||||
element: &Element,
|
||||
rect: Rect,
|
||||
@@ -256,8 +266,9 @@ fn layout_element(
|
||||
perf_stats.nodes += 1;
|
||||
|
||||
// Viewport culling: skip fully off-screen elements inside a scroll box.
|
||||
if let Some(clip) = clip_rect {
|
||||
if !rects_overlap(rect, clip) {
|
||||
if let Some(clip) = clip_rect
|
||||
&& !rects_overlap(rect, clip)
|
||||
{
|
||||
perf_stats.viewport_culled += 1;
|
||||
return LayoutNode {
|
||||
path,
|
||||
@@ -274,7 +285,6 @@ fn layout_element(
|
||||
children: Vec::new(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Incremental layout cache check.
|
||||
// Round to nearest pixel to eliminate floating-point representation
|
||||
@@ -368,7 +378,13 @@ fn layout_element(
|
||||
if pushed_clip {
|
||||
scene.pop_clip();
|
||||
}
|
||||
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
|
||||
cache_layout(
|
||||
layout_cache,
|
||||
cache_key,
|
||||
&interaction,
|
||||
&scene.items[scene_start..],
|
||||
rect.origin,
|
||||
);
|
||||
return interaction;
|
||||
}
|
||||
|
||||
@@ -382,7 +398,13 @@ fn layout_element(
|
||||
if pushed_clip {
|
||||
scene.pop_clip();
|
||||
}
|
||||
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
|
||||
cache_layout(
|
||||
layout_cache,
|
||||
cache_key,
|
||||
&interaction,
|
||||
&scene.items[scene_start..],
|
||||
rect.origin,
|
||||
);
|
||||
return interaction;
|
||||
}
|
||||
|
||||
@@ -508,7 +530,13 @@ fn layout_element(
|
||||
if pushed_clip {
|
||||
scene.pop_clip();
|
||||
}
|
||||
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
|
||||
cache_layout(
|
||||
layout_cache,
|
||||
cache_key,
|
||||
&interaction,
|
||||
&scene.items[scene_start..],
|
||||
rect.origin,
|
||||
);
|
||||
return interaction;
|
||||
}
|
||||
|
||||
@@ -518,7 +546,13 @@ fn layout_element(
|
||||
if pushed_clip {
|
||||
scene.pop_clip();
|
||||
}
|
||||
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
|
||||
cache_layout(
|
||||
layout_cache,
|
||||
cache_key,
|
||||
&interaction,
|
||||
&scene.items[scene_start..],
|
||||
rect.origin,
|
||||
);
|
||||
return interaction;
|
||||
}
|
||||
|
||||
@@ -527,7 +561,13 @@ fn layout_element(
|
||||
if pushed_clip {
|
||||
scene.pop_clip();
|
||||
}
|
||||
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
|
||||
cache_layout(
|
||||
layout_cache,
|
||||
cache_key,
|
||||
&interaction,
|
||||
&scene.items[scene_start..],
|
||||
rect.origin,
|
||||
);
|
||||
return interaction;
|
||||
}
|
||||
interaction.children = layout_container_children(
|
||||
@@ -545,7 +585,13 @@ fn layout_element(
|
||||
scene.pop_clip();
|
||||
}
|
||||
|
||||
cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin);
|
||||
cache_layout(
|
||||
layout_cache,
|
||||
cache_key,
|
||||
&interaction,
|
||||
&scene.items[scene_start..],
|
||||
rect.origin,
|
||||
);
|
||||
interaction
|
||||
}
|
||||
|
||||
@@ -558,10 +604,13 @@ fn cache_layout(
|
||||
origin: Point,
|
||||
) {
|
||||
let neg = Point::new(-origin.x, -origin.y);
|
||||
cache.results.insert(key, CachedLayout {
|
||||
cache.results.insert(
|
||||
key,
|
||||
CachedLayout {
|
||||
interaction_node: interaction.clone().translated(neg),
|
||||
scene_items: scene_items.iter().map(|i| i.translated(neg)).collect(),
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn hit_path_node(node: &LayoutNode, point: crate::scene::Point) -> Option<Vec<HitTarget>> {
|
||||
@@ -735,6 +784,7 @@ struct MeasuredChild {
|
||||
is_flex: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_container_children(
|
||||
element: &Element,
|
||||
content: Rect,
|
||||
@@ -986,7 +1036,14 @@ fn intrinsic_size(
|
||||
return cached;
|
||||
}
|
||||
let insets = content_insets(&element.style);
|
||||
let result = intrinsic_size_inner(element, available_size, insets, text_system, perf_stats, layout_cache);
|
||||
let result = intrinsic_size_inner(
|
||||
element,
|
||||
available_size,
|
||||
insets,
|
||||
text_system,
|
||||
perf_stats,
|
||||
layout_cache,
|
||||
);
|
||||
layout_cache.intrinsic_cache.insert(cache_key, result);
|
||||
result
|
||||
}
|
||||
@@ -1047,8 +1104,13 @@ fn intrinsic_size_inner(
|
||||
);
|
||||
}
|
||||
|
||||
let intrinsic_content =
|
||||
intrinsic_container_content_size(element, content_size, text_system, perf_stats, layout_cache);
|
||||
let intrinsic_content = intrinsic_container_content_size(
|
||||
element,
|
||||
content_size,
|
||||
text_system,
|
||||
perf_stats,
|
||||
layout_cache,
|
||||
);
|
||||
|
||||
UiSize::new(
|
||||
explicit_width
|
||||
@@ -1539,9 +1601,9 @@ fn child_rect(
|
||||
mod tests {
|
||||
use super::{LayoutCache, layout_scene, layout_snapshot, layout_snapshot_with_cache};
|
||||
use crate::scene::{Color, DisplayItem, Point, Quad, Rect, UiSize};
|
||||
use crate::text::TextSystem;
|
||||
use crate::text::{TextStyle, TextWrap};
|
||||
use crate::tree::{Edges, Element, ElementId, FlexDirection};
|
||||
use crate::text::TextSystem;
|
||||
|
||||
#[test]
|
||||
fn row_layout_apportions_fixed_and_flex_children() {
|
||||
@@ -2156,10 +2218,12 @@ mod tests {
|
||||
let size = UiSize::new(400.0, 600.0);
|
||||
let snap1 = layout_snapshot_with_cache(1, size, &root, &mut text_system, &mut layout_cache);
|
||||
let snap2 = layout_snapshot_with_cache(2, size, &root, &mut text_system, &mut layout_cache);
|
||||
assert_eq!(snap1.scene.items, snap2.scene.items, "scene items should be identical");
|
||||
assert_eq!(
|
||||
snap1.interaction_tree.root,
|
||||
snap2.interaction_tree.root,
|
||||
snap1.scene.items, snap2.scene.items,
|
||||
"scene items should be identical"
|
||||
);
|
||||
assert_eq!(
|
||||
snap1.interaction_tree.root, snap2.interaction_tree.root,
|
||||
"interaction trees should be identical"
|
||||
);
|
||||
}
|
||||
@@ -2198,12 +2262,14 @@ mod tests {
|
||||
let item_height = 40.0;
|
||||
let viewport_height = 200.0;
|
||||
let n = 100;
|
||||
let mut scroll_box = Element::scroll_box(0.0).width(400.0).height(viewport_height);
|
||||
let mut scroll_box = Element::scroll_box(0.0)
|
||||
.width(400.0)
|
||||
.height(viewport_height);
|
||||
for i in 0..n {
|
||||
scroll_box = scroll_box.child(
|
||||
Element::new()
|
||||
.height(item_height)
|
||||
.child(Element::text(format!("item {i}"), text_style()))
|
||||
.child(Element::text(format!("item {i}"), text_style())),
|
||||
);
|
||||
}
|
||||
let root = Element::new().child(scroll_box);
|
||||
@@ -2214,11 +2280,20 @@ mod tests {
|
||||
let snap = layout_snapshot_with_cache(1, size, &root, &mut text_system, &mut layout_cache);
|
||||
|
||||
// Only text items within the 200px viewport should appear in the scene.
|
||||
let text_items = snap.scene.items.iter().filter(|item| {
|
||||
matches!(item, DisplayItem::Text(_))
|
||||
}).count();
|
||||
let text_items = snap
|
||||
.scene
|
||||
.items
|
||||
.iter()
|
||||
.filter(|item| matches!(item, DisplayItem::Text(_)))
|
||||
.count();
|
||||
// At most 6 text items should be visible (5 fit + 1 partial).
|
||||
assert!(text_items <= 6, "expected <= 6 text items in scene, got {text_items} (culling not working)");
|
||||
assert!(text_items >= 4, "expected >= 4 text items visible, got {text_items}");
|
||||
assert!(
|
||||
text_items <= 6,
|
||||
"expected <= 6 text items in scene, got {text_items} (culling not working)"
|
||||
);
|
||||
assert!(
|
||||
text_items >= 4,
|
||||
"expected >= 4 text items visible, got {text_items}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
pub(crate) mod trace_targets {
|
||||
pub const PLATFORM: &str = "ruin_ui::platform";
|
||||
pub const SCENE: &str = "ruin_ui::scene";
|
||||
#[allow(dead_code)]
|
||||
pub const TEXT_PERF: &str = "ruin_ui::text_perf";
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ pub struct PlatformProxy {
|
||||
pub struct PlatformRuntime {
|
||||
proxy: PlatformProxy,
|
||||
events: mpsc::Receiver<PlatformEvent>,
|
||||
_worker: WorkerHandle,
|
||||
worker: Option<WorkerHandle>,
|
||||
}
|
||||
|
||||
pub struct PlatformEndpoint {
|
||||
@@ -73,6 +73,7 @@ pub enum PlatformEvent {
|
||||
window_id: WindowId,
|
||||
text: String,
|
||||
},
|
||||
#[cfg(target_os = "linux")]
|
||||
PrimarySelectionText {
|
||||
window_id: WindowId,
|
||||
text: String,
|
||||
@@ -118,10 +119,12 @@ pub enum PlatformRequest {
|
||||
RequestClipboardText {
|
||||
window_id: WindowId,
|
||||
},
|
||||
#[cfg(target_os = "linux")]
|
||||
SetPrimarySelectionText {
|
||||
window_id: WindowId,
|
||||
text: String,
|
||||
},
|
||||
#[cfg(target_os = "linux")]
|
||||
RequestPrimarySelectionText {
|
||||
window_id: WindowId,
|
||||
},
|
||||
@@ -203,7 +206,30 @@ impl PlatformRuntime {
|
||||
Self {
|
||||
proxy,
|
||||
events: event_rx,
|
||||
_worker: worker,
|
||||
worker: Some(worker),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a platform runtime hosted by the current runtime thread.
|
||||
///
|
||||
/// This is useful for backends that must run on a specific host thread
|
||||
/// (for example, AppKit on macOS main thread).
|
||||
pub fn custom_local(start: impl FnOnce(PlatformEndpoint)) -> Self {
|
||||
let (command_tx, command_rx) = mpsc::unbounded_channel::<PlatformRequest>();
|
||||
let (event_tx, event_rx) = mpsc::unbounded_channel::<PlatformEvent>();
|
||||
let proxy = PlatformProxy {
|
||||
command_tx,
|
||||
next_window_id: Arc::new(AtomicU64::new(1)),
|
||||
};
|
||||
start(PlatformEndpoint {
|
||||
commands: command_rx,
|
||||
events: event_tx,
|
||||
});
|
||||
|
||||
Self {
|
||||
proxy,
|
||||
events: event_rx,
|
||||
worker: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +242,7 @@ impl PlatformRuntime {
|
||||
}
|
||||
|
||||
pub fn take_pending_events(&mut self) -> Vec<PlatformEvent> {
|
||||
let _ = self.worker.as_ref();
|
||||
let mut events = Vec::new();
|
||||
while let Ok(event) = self.events.try_recv() {
|
||||
events.push(event);
|
||||
@@ -247,6 +274,7 @@ impl PlatformProxy {
|
||||
self.send(PlatformRequest::ReplaceScene { window_id, scene })
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn set_primary_selection_text(
|
||||
&self,
|
||||
window_id: WindowId,
|
||||
@@ -258,6 +286,7 @@ impl PlatformProxy {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn request_primary_selection_text(
|
||||
&self,
|
||||
window_id: WindowId,
|
||||
@@ -356,7 +385,9 @@ pub fn start_headless() -> PlatformRuntime {
|
||||
}
|
||||
PlatformRequest::SetClipboardText { .. } => {}
|
||||
PlatformRequest::RequestClipboardText { .. } => {}
|
||||
#[cfg(target_os = "linux")]
|
||||
PlatformRequest::SetPrimarySelectionText { .. } => {}
|
||||
#[cfg(target_os = "linux")]
|
||||
PlatformRequest::RequestPrimarySelectionText { .. } => {}
|
||||
PlatformRequest::SetCursorIcon { .. } => {}
|
||||
PlatformRequest::EmitCloseRequested { window_id } => {
|
||||
|
||||
@@ -110,6 +110,7 @@ impl WindowController {
|
||||
}
|
||||
|
||||
/// Copies plain text to the platform primary-selection buffer for this window.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn set_primary_selection_text(
|
||||
&self,
|
||||
text: impl Into<String>,
|
||||
@@ -128,6 +129,7 @@ impl WindowController {
|
||||
}
|
||||
|
||||
/// Requests the current plain-text primary selection contents from the platform.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn request_primary_selection_text(&self) -> Result<(), PlatformClosed> {
|
||||
self.proxy.request_primary_selection_text(self.id)
|
||||
}
|
||||
|
||||
@@ -219,6 +219,7 @@ impl Deref for PreparedText {
|
||||
|
||||
impl PreparedText {
|
||||
/// Construct a `PreparedText` from shaped data. Called by `TextSystem::prepare_spans`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn from_layout(
|
||||
element_id: Option<ElementId>,
|
||||
text: String,
|
||||
@@ -623,10 +624,18 @@ impl DisplayItem {
|
||||
/// Return a copy of this item with all positions shifted by `offset`.
|
||||
pub fn translated(&self, offset: Point) -> Self {
|
||||
fn translate_rect(r: Rect, o: Point) -> Rect {
|
||||
Rect::new(r.origin.x + o.x, r.origin.y + o.y, r.size.width, r.size.height)
|
||||
Rect::new(
|
||||
r.origin.x + o.x,
|
||||
r.origin.y + o.y,
|
||||
r.size.width,
|
||||
r.size.height,
|
||||
)
|
||||
}
|
||||
match self {
|
||||
Self::Quad(q) => Self::Quad(Quad { rect: translate_rect(q.rect, offset), ..*q }),
|
||||
Self::Quad(q) => Self::Quad(Quad {
|
||||
rect: translate_rect(q.rect, offset),
|
||||
..*q
|
||||
}),
|
||||
Self::RoundedRect(r) => Self::RoundedRect(RoundedRect {
|
||||
rect: translate_rect(r.rect, offset),
|
||||
..*r
|
||||
@@ -838,8 +847,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn prepared_text_vertical_offset_moves_between_lines() {
|
||||
use std::sync::Arc;
|
||||
use super::{PreparedTextLine, TextLayoutData};
|
||||
use std::sync::Arc;
|
||||
|
||||
let mut text = PreparedText::monospace(
|
||||
"abcdwxyz",
|
||||
@@ -873,7 +882,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
let orig_size = text.layout.size;
|
||||
text.layout = Arc::new(TextLayoutData { lines, glyphs, size: orig_size });
|
||||
text.layout = Arc::new(TextLayoutData {
|
||||
lines,
|
||||
glyphs,
|
||||
size: orig_size,
|
||||
});
|
||||
|
||||
assert_eq!(text.vertical_offset(2, 1), Some(6));
|
||||
assert_eq!(text.vertical_offset(6, -1), Some(2));
|
||||
@@ -892,8 +905,12 @@ mod tests {
|
||||
let target_line = &text.lines[1];
|
||||
// Lines store LOCAL coords; add text.origin to get absolute window coords for the query.
|
||||
let y = text.origin.y + target_line.rect.origin.y + target_line.rect.size.height * 0.5;
|
||||
let start = text.byte_offset_for_position(Point::new(text.origin.x + target_line.rect.origin.x, y));
|
||||
let end = text.byte_offset_for_position(Point::new(text.origin.x + target_line.rect.origin.x + 16.0, y));
|
||||
let start =
|
||||
text.byte_offset_for_position(Point::new(text.origin.x + target_line.rect.origin.x, y));
|
||||
let end = text.byte_offset_for_position(Point::new(
|
||||
text.origin.x + target_line.rect.origin.x + 16.0,
|
||||
y,
|
||||
));
|
||||
let rects = text.selection_rects(start, end);
|
||||
|
||||
assert_eq!(rects.len(), 1);
|
||||
|
||||
@@ -10,8 +10,9 @@ use cosmic_text::{
|
||||
};
|
||||
use fontconfig::Fontconfig;
|
||||
|
||||
use crate::{Color, GlyphInstance, Point, PreparedText, PreparedTextLine, Rect, TextLayoutData,
|
||||
UiSize};
|
||||
use crate::{
|
||||
Color, GlyphInstance, Point, PreparedText, PreparedTextLine, Rect, TextLayoutData, UiSize,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum TextAlign {
|
||||
@@ -443,7 +444,11 @@ impl TextSystem {
|
||||
// For no-wrap + start-aligned text, width doesn't affect shaping.
|
||||
// Pass None so cosmic-text doesn't truncate and the result is
|
||||
// consistent with the width-independent cache key.
|
||||
let effective_width = if width_affects_layout(style) { width } else { None };
|
||||
let effective_width = if width_affects_layout(style) {
|
||||
width
|
||||
} else {
|
||||
None
|
||||
};
|
||||
borrowed.set_size(effective_width, None);
|
||||
let default_attrs = default_attrs_for_style(style, default_family.as_deref());
|
||||
if uses_plain_text_fast_path {
|
||||
@@ -495,9 +500,7 @@ impl TextSystem {
|
||||
// is color-independent. Per-span colors (from glyph.color_opt)
|
||||
// are stored directly since they are part of the shape key via
|
||||
// the spans hash.
|
||||
color: glyph
|
||||
.color_opt
|
||||
.map_or(Color::SENTINEL, color_from_cosmic),
|
||||
color: glyph.color_opt.map_or(Color::SENTINEL, color_from_cosmic),
|
||||
cache_key: Some(physical.cache_key),
|
||||
text_start: line_offset + glyph.start,
|
||||
text_end: line_offset + glyph.end,
|
||||
@@ -930,7 +933,9 @@ impl std::hash::Hash for TextStyle {
|
||||
self.line_height.to_bits().hash(state);
|
||||
self.color.hash(state);
|
||||
self.font_family.hash(state);
|
||||
self.bounds.map(|b| (b.width.to_bits(), b.height.to_bits())).hash(state);
|
||||
self.bounds
|
||||
.map(|b| (b.width.to_bits(), b.height.to_bits()))
|
||||
.hash(state);
|
||||
self.wrap.hash(state);
|
||||
self.align.hash(state);
|
||||
self.max_lines.hash(state);
|
||||
@@ -1038,16 +1043,32 @@ mod tests {
|
||||
let blue = text_system.prepare("hello", origin, &style_blue);
|
||||
// Both PreparedTexts must have identical glyph shapes and sentinel colors —
|
||||
// the second call hits the shape cache and produces the same layout data.
|
||||
assert_eq!(red.glyphs.len(), blue.glyphs.len(), "glyph count must match");
|
||||
assert_eq!(
|
||||
red.glyphs.len(),
|
||||
blue.glyphs.len(),
|
||||
"glyph count must match"
|
||||
);
|
||||
for (r, b) in red.glyphs.iter().zip(blue.glyphs.iter()) {
|
||||
assert_eq!(r.position, b.position, "glyph positions must match");
|
||||
assert_eq!(r.cache_key, b.cache_key, "glyph cache keys must match");
|
||||
assert_eq!(r.color, Color::SENTINEL, "default-color glyphs must use SENTINEL");
|
||||
assert_eq!(b.color, Color::SENTINEL, "default-color glyphs must use SENTINEL");
|
||||
assert_eq!(
|
||||
r.color,
|
||||
Color::SENTINEL,
|
||||
"default-color glyphs must use SENTINEL"
|
||||
);
|
||||
assert_eq!(
|
||||
b.color,
|
||||
Color::SENTINEL,
|
||||
"default-color glyphs must use SENTINEL"
|
||||
);
|
||||
}
|
||||
// PreparedText::clone() is an Arc clone — O(1).
|
||||
let cloned = red.clone();
|
||||
assert_eq!(cloned.layout_ptr(), red.layout_ptr(), "clone must share the same Arc");
|
||||
assert_eq!(
|
||||
cloned.layout_ptr(),
|
||||
red.layout_ptr(),
|
||||
"clone must share the same Arc"
|
||||
);
|
||||
// But they should carry different default colors.
|
||||
assert_eq!(red.default_color, Color::rgb(0xFF, 0x00, 0x00));
|
||||
assert_eq!(blue.default_color, Color::rgb(0x00, 0x00, 0xFF));
|
||||
|
||||
15
lib/ui_platform_macos/Cargo.toml
Normal file
15
lib/ui_platform_macos/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "ruin_ui_platform_macos"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
objc2 = "0.6.4"
|
||||
objc2-core-foundation = "0.3.2"
|
||||
objc2-foundation = "0.3.2"
|
||||
raw-window-handle = "0.6"
|
||||
ruin_ui = { path = "../ui" }
|
||||
ruin_ui_renderer_wgpu = { path = "../ui_renderer_wgpu" }
|
||||
ruin_runtime = { package = "ruin-runtime", path = "../runtime" }
|
||||
tracing = "0.1"
|
||||
1058
lib/ui_platform_macos/src/lib.rs
Normal file
1058
lib/ui_platform_macos/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1309,7 +1309,9 @@ fn spawn_window_worker(
|
||||
"worker observed resized frame"
|
||||
);
|
||||
state_ref.pending_viewport = Some(current_viewport);
|
||||
state_ref.pending_viewport_since.get_or_insert_with(Instant::now);
|
||||
state_ref
|
||||
.pending_viewport_since
|
||||
.get_or_insert_with(Instant::now);
|
||||
// Emit configure BEFORE touching the GPU so the app
|
||||
// thread starts layout immediately in parallel.
|
||||
// The actual swapchain recreation is deferred to just
|
||||
@@ -1354,7 +1356,9 @@ fn spawn_window_worker(
|
||||
"scene size does not match current viewport; holding last buffer"
|
||||
);
|
||||
state_ref.pending_viewport = Some(current_viewport);
|
||||
state_ref.pending_viewport_since.get_or_insert_with(Instant::now);
|
||||
state_ref
|
||||
.pending_viewport_since
|
||||
.get_or_insert_with(Instant::now);
|
||||
state_ref.window.request_redraw();
|
||||
} else if !state_ref.window.presentation_ready() {
|
||||
// Correct scene is ready but the compositor
|
||||
@@ -1365,7 +1369,9 @@ fn spawn_window_worker(
|
||||
// before rendering. Deferring from frame.resized means
|
||||
// rapid configures pay one recreation instead of one
|
||||
// per event.
|
||||
if let Some((w, h)) = state_ref.pending_swapchain_size.take() {
|
||||
if let Some((w, h)) =
|
||||
state_ref.pending_swapchain_size.take()
|
||||
{
|
||||
let t_resize = std::time::Instant::now();
|
||||
state_ref.renderer.resize(w, h);
|
||||
let resize_gpu_us = t_resize.elapsed().as_micros();
|
||||
@@ -1382,7 +1388,8 @@ fn spawn_window_worker(
|
||||
let t_render = std::time::Instant::now();
|
||||
match state_ref.renderer.render(scene) {
|
||||
Ok(()) => {
|
||||
let render_gpu_us = t_render.elapsed().as_micros();
|
||||
let render_gpu_us =
|
||||
t_render.elapsed().as_micros();
|
||||
debug!(
|
||||
target: "ruin_ui_platform_wayland::perf",
|
||||
window_id = state_ref.window_id.raw(),
|
||||
@@ -1464,10 +1471,10 @@ fn spawn_window_worker(
|
||||
{
|
||||
let delay =
|
||||
repeat.next_at.saturating_duration_since(Instant::now());
|
||||
state_ref.keyboard_repeat_timer = Some(set_timeout(
|
||||
delay,
|
||||
move || write_wakeup(pipe_write_fd),
|
||||
));
|
||||
state_ref.keyboard_repeat_timer =
|
||||
Some(set_timeout(delay, move || {
|
||||
write_wakeup(pipe_write_fd)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -990,7 +990,9 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
|
||||
bottom: physical.y - image.placement.top + height,
|
||||
},
|
||||
content: image.content,
|
||||
color: glyph.color_opt.map_or(text.default_color, color_from_cosmic),
|
||||
color: glyph
|
||||
.color_opt
|
||||
.map_or(text.default_color, color_from_cosmic),
|
||||
data: image.data.clone(),
|
||||
});
|
||||
}
|
||||
@@ -1470,7 +1472,11 @@ fn create_glyph_atlas(
|
||||
/// Resolve `Color::SENTINEL` to `default_color`; return other colors unchanged.
|
||||
#[inline]
|
||||
fn resolve_glyph_color(color: Color, default_color: Color) -> Color {
|
||||
if color == Color::SENTINEL { default_color } else { color }
|
||||
if color == Color::SENTINEL {
|
||||
default_color
|
||||
} else {
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
fn color_from_cosmic(color: cosmic_text::Color) -> Color {
|
||||
@@ -2297,7 +2303,7 @@ fn clip_textured_rect(
|
||||
/// return only those glyph indices. This turns O(all_glyphs) iteration into
|
||||
/// O(log(lines) + visible_glyphs) — critical for large text nodes in scroll
|
||||
/// boxes where only a few lines are on screen at once.
|
||||
fn clip_visible_glyphs<'a>(text: &'a PreparedText, clip: Option<Rect>) -> &'a [GlyphInstance] {
|
||||
fn clip_visible_glyphs(text: &PreparedText, clip: Option<Rect>) -> &[GlyphInstance] {
|
||||
let Some(clip) = clip else {
|
||||
return &text.glyphs;
|
||||
};
|
||||
@@ -2332,7 +2338,12 @@ fn text_texture_key(text: &PreparedText) -> TextTextureKey {
|
||||
.map(|bounds| (bounds.width.to_bits(), bounds.height.to_bits())),
|
||||
font_size_bits: text.font_size.to_bits(),
|
||||
line_height_bits: text.line_height.to_bits(),
|
||||
color: (text.default_color.r, text.default_color.g, text.default_color.b, text.default_color.a),
|
||||
color: (
|
||||
text.default_color.r,
|
||||
text.default_color.g,
|
||||
text.default_color.b,
|
||||
text.default_color.a,
|
||||
),
|
||||
glyphs: text
|
||||
.glyphs
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user