mirror of
https://github.com/lune-org/lune.git
synced 2025-05-04 10:43:57 +01:00
Compare commits
20 commits
Author | SHA1 | Date | |
---|---|---|---|
|
df56cd58e7 | ||
|
66e3b58cd7 | ||
|
fb33d1812d | ||
|
0ddaaaefb5 | ||
|
2e5b3bb5eb | ||
|
6645631c46 | ||
|
120048ae95 | ||
|
2d8e58b028 | ||
|
b1fc60023d | ||
|
1429450a64 | ||
|
d2a89f41c8 | ||
|
9c9b90d70d | ||
|
d425d2568a | ||
|
4c2bbcf425 | ||
|
461ca24c33 | ||
|
7fd390dead | ||
|
c35eaa7899 | ||
|
b57fa6fad3 | ||
|
3e80a0a1c4 | ||
|
ac8c809a20 |
63 changed files with 1744 additions and 660 deletions
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -8,6 +8,31 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for non-UTF8 strings in arguments to `process.exec` and `process.spawn`
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved cross-platform compatibility and correctness for values in `process.args` and `process.env`, especially on Windows
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed various crashes during require that had the error `cannot mutably borrow app data container`
|
||||
|
||||
## `0.9.2` - April 30th, 2025
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved performance of `net.request` and `net.serve` when handling large request bodies
|
||||
- Improved performance and memory usage of `task.spawn`, `task.defer`, and `task.delay`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed accidental breakage of `net.request` in version `0.9.1`
|
||||
|
||||
## `0.9.1` - April 29th, 2025
|
||||
|
||||
### Added
|
||||
|
|
36
Cargo.lock
generated
36
Cargo.lock
generated
|
@ -1655,7 +1655,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-fs",
|
||||
|
@ -1670,6 +1670,7 @@ dependencies = [
|
|||
"lune-utils",
|
||||
"mlua",
|
||||
"mlua-luau-scheduler",
|
||||
"rustls",
|
||||
"rustyline",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -1682,7 +1683,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-roblox"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"glam",
|
||||
"lune-utils",
|
||||
|
@ -1698,7 +1699,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-std"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-fs",
|
||||
|
@ -1722,7 +1723,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-std-datetime"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono_lc",
|
||||
|
@ -1733,7 +1734,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-std-fs"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"async-fs",
|
||||
"bstr",
|
||||
|
@ -1745,7 +1746,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-std-luau"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"lune-utils",
|
||||
"mlua",
|
||||
|
@ -1753,7 +1754,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-std-net"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-executor",
|
||||
|
@ -1763,6 +1764,7 @@ dependencies = [
|
|||
"async-tungstenite",
|
||||
"blocking",
|
||||
"bstr",
|
||||
"form_urlencoded",
|
||||
"futures",
|
||||
"futures-lite",
|
||||
"futures-rustls",
|
||||
|
@ -1783,7 +1785,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-std-process"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-lock",
|
||||
|
@ -1797,13 +1799,12 @@ dependencies = [
|
|||
"lune-utils",
|
||||
"mlua",
|
||||
"mlua-luau-scheduler",
|
||||
"os_str_bytes",
|
||||
"pin-project",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lune-std-regex"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"lune-utils",
|
||||
"mlua",
|
||||
|
@ -1813,7 +1814,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-std-roblox"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"lune-roblox",
|
||||
"lune-utils",
|
||||
|
@ -1825,7 +1826,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-std-serde"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"blake3",
|
||||
|
@ -1849,7 +1850,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-std-stdio"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
|
@ -1863,7 +1864,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-std-task"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"futures-lite",
|
||||
|
@ -1874,11 +1875,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lune-utils"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"console",
|
||||
"dunce",
|
||||
"mlua",
|
||||
"os_str_bytes",
|
||||
"parking_lot",
|
||||
"path-clean",
|
||||
"pathdiff",
|
||||
|
@ -1986,14 +1988,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mlua-luau-scheduler"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"async-executor",
|
||||
"async-fs",
|
||||
"async-io",
|
||||
"blocking",
|
||||
"concurrent-queue",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"mlua",
|
||||
"rustc-hash 2.1.1",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-roblox"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -25,4 +25,4 @@ rbx_reflection = "5.0"
|
|||
rbx_reflection_database = "1.0"
|
||||
rbx_xml = "1.0"
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std-datetime"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -19,4 +19,4 @@ thiserror = "2.0"
|
|||
chrono = "0.4.38"
|
||||
chrono_lc = "0.1.6"
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std-fs"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -19,5 +19,5 @@ async-fs = "2.1"
|
|||
bstr = "1.9"
|
||||
futures-lite = "2.6"
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-std-datetime = { version = "0.2.1", path = "../lune-std-datetime" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
lune-std-datetime = { version = "0.2.2", path = "../lune-std-datetime" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std-luau"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -15,4 +15,4 @@ workspace = true
|
|||
[dependencies]
|
||||
mlua = { version = "0.10.3", features = ["luau", "luau-jit"] }
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std-net"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -14,7 +14,7 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
mlua = { version = "0.10.3", features = ["luau"] }
|
||||
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
|
||||
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
|
||||
|
||||
async-channel = "2.3"
|
||||
async-executor = "1.13"
|
||||
|
@ -24,6 +24,7 @@ async-net = "2.0"
|
|||
async-tungstenite = "0.29"
|
||||
blocking = "1.6"
|
||||
bstr = "1.9"
|
||||
form_urlencoded = "1.2"
|
||||
futures = { version = "0.3", default-features = false, features = ["std"] }
|
||||
futures-lite = "2.6"
|
||||
futures-rustls = "0.26"
|
||||
|
@ -37,5 +38,5 @@ urlencoding = "2.1"
|
|||
webpki = "0.22"
|
||||
webpki-roots = "0.26"
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-std-serde = { version = "0.2.1", path = "../lune-std-serde" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
lune-std-serde = { version = "0.2.2", path = "../lune-std-serde" }
|
||||
|
|
59
crates/lune-std-net/src/body/cursor.rs
Normal file
59
crates/lune-std-net/src/body/cursor.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use hyper::body::{Buf, Bytes};
|
||||
|
||||
use super::inner::ReadableBodyInner;
|
||||
|
||||
/**
|
||||
The cursor keeping track of inner data and its position for a readable body.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReadableBodyCursor {
|
||||
inner: ReadableBodyInner,
|
||||
start: usize,
|
||||
}
|
||||
|
||||
impl ReadableBodyCursor {
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
&self.inner.as_slice()[self.start..]
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, cnt: usize) {
|
||||
self.start += cnt;
|
||||
if self.start > self.inner.len() {
|
||||
self.start = self.inner.len();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> Bytes {
|
||||
self.inner.into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl Buf for ReadableBodyCursor {
|
||||
fn remaining(&self) -> usize {
|
||||
self.len().saturating_sub(self.start)
|
||||
}
|
||||
|
||||
fn chunk(&self) -> &[u8] {
|
||||
self.as_slice()
|
||||
}
|
||||
|
||||
fn advance(&mut self, cnt: usize) {
|
||||
self.advance(cnt);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for ReadableBodyCursor
|
||||
where
|
||||
T: Into<ReadableBodyInner>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self {
|
||||
inner: value.into(),
|
||||
start: 0,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use http_body_util::{BodyExt, Full};
|
||||
use http_body_util::BodyExt;
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming},
|
||||
header::CONTENT_ENCODING,
|
||||
|
@ -33,11 +33,3 @@ pub async fn handle_incoming_body(
|
|||
|
||||
Ok((body, was_decompressed))
|
||||
}
|
||||
|
||||
pub fn bytes_to_full(bytes: Bytes) -> Full<Bytes> {
|
||||
if bytes.is_empty() {
|
||||
Full::default()
|
||||
} else {
|
||||
Full::new(bytes)
|
||||
}
|
||||
}
|
110
crates/lune-std-net/src/body/inner.rs
Normal file
110
crates/lune-std-net/src/body/inner.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
use hyper::body::{Buf as _, Bytes};
|
||||
use mlua::{prelude::*, Buffer as LuaBuffer};
|
||||
|
||||
/**
|
||||
The inner data for a readable body.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ReadableBodyInner {
|
||||
Bytes(Bytes),
|
||||
String(String),
|
||||
LuaString(LuaString),
|
||||
LuaBuffer(LuaBuffer),
|
||||
}
|
||||
|
||||
impl ReadableBodyInner {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Bytes(b) => b.len(),
|
||||
Self::String(s) => s.len(),
|
||||
Self::LuaString(s) => s.as_bytes().len(),
|
||||
Self::LuaBuffer(b) => b.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
/*
|
||||
SAFETY: Reading lua strings and lua buffers as raw slices is safe while we can
|
||||
guarantee that the inner Lua value + main lua struct has not yet been dropped
|
||||
|
||||
1. Buffers are fixed-size and guaranteed to never resize
|
||||
2. We do not expose any method for writing to the body, only reading
|
||||
3. We guarantee that net.request and net.serve futures are only driven forward
|
||||
while we also know that the Lua + scheduler pair have not yet been dropped
|
||||
4. Any writes from within lua to a buffer, are considered user error,
|
||||
and are not unsafe, since the only possible outcome with the above
|
||||
guarantees is invalid / mangled contents in request / response bodies
|
||||
*/
|
||||
match self {
|
||||
Self::Bytes(b) => b.chunk(),
|
||||
Self::String(s) => s.as_bytes(),
|
||||
Self::LuaString(s) => unsafe {
|
||||
// BorrowedBytes would not let us return a plain slice here,
|
||||
// which is what the Buf implementation below needs - we need to
|
||||
// do a little hack here to re-create the slice without a lifetime
|
||||
let b = s.as_bytes();
|
||||
|
||||
let ptr = b.as_ptr();
|
||||
let len = b.len();
|
||||
|
||||
std::slice::from_raw_parts(ptr, len)
|
||||
},
|
||||
Self::LuaBuffer(b) => unsafe {
|
||||
// Similar to above, we need to get the raw slice for the buffer,
|
||||
// which is a bit trickier here because Buffer has a read + write
|
||||
// interface instead of using slices for some unknown reason
|
||||
let v = LuaValue::Buffer(b.clone());
|
||||
|
||||
let ptr = v.to_pointer().cast::<u8>();
|
||||
let len = b.len();
|
||||
|
||||
std::slice::from_raw_parts(ptr, len)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> Bytes {
|
||||
match self {
|
||||
Self::Bytes(b) => b,
|
||||
Self::String(s) => Bytes::from(s),
|
||||
Self::LuaString(s) => Bytes::from(s.as_bytes().to_vec()),
|
||||
Self::LuaBuffer(b) => Bytes::from(b.to_vec()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for ReadableBodyInner {
|
||||
fn from(value: &'static str) -> Self {
|
||||
Self::Bytes(Bytes::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for ReadableBodyInner {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Self::Bytes(Bytes::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bytes> for ReadableBodyInner {
|
||||
fn from(value: Bytes) -> Self {
|
||||
Self::Bytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ReadableBodyInner {
|
||||
fn from(value: String) -> Self {
|
||||
Self::String(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LuaString> for ReadableBodyInner {
|
||||
fn from(value: LuaString) -> Self {
|
||||
Self::LuaString(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LuaBuffer> for ReadableBodyInner {
|
||||
fn from(value: LuaBuffer) -> Self {
|
||||
Self::LuaBuffer(value)
|
||||
}
|
||||
}
|
11
crates/lune-std-net/src/body/mod.rs
Normal file
11
crates/lune-std-net/src/body/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#![allow(unused_imports)]
|
||||
|
||||
mod cursor;
|
||||
mod incoming;
|
||||
mod inner;
|
||||
mod readable;
|
||||
|
||||
pub use self::cursor::ReadableBodyCursor;
|
||||
pub use self::incoming::handle_incoming_body;
|
||||
pub use self::inner::ReadableBodyInner;
|
||||
pub use self::readable::ReadableBody;
|
105
crates/lune-std-net/src/body/readable.rs
Normal file
105
crates/lune-std-net/src/body/readable.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use std::convert::Infallible;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use hyper::body::{Body, Bytes, Frame, SizeHint};
|
||||
use mlua::prelude::*;
|
||||
|
||||
use super::cursor::ReadableBodyCursor;
|
||||
|
||||
/**
|
||||
Zero-copy wrapper for a readable body.
|
||||
|
||||
Provides methods to read bytes that can be safely used if, and only
|
||||
if, the respective Lua struct for the body has not yet been dropped.
|
||||
|
||||
If the body was created from a `Vec<u8>`, `Bytes`, or a `String`, reading
|
||||
bytes is always safe and does not go through any additional indirections.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReadableBody {
|
||||
cursor: Option<ReadableBodyCursor>,
|
||||
}
|
||||
|
||||
impl ReadableBody {
|
||||
pub const fn empty() -> Self {
|
||||
Self { cursor: None }
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
match self.cursor.as_ref() {
|
||||
Some(cursor) => cursor.as_slice(),
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> Bytes {
|
||||
match self.cursor {
|
||||
Some(cursor) => cursor.into_bytes(),
|
||||
None => Bytes::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Body for ReadableBody {
|
||||
type Data = ReadableBodyCursor;
|
||||
type Error = Infallible;
|
||||
|
||||
fn poll_frame(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
|
||||
Poll::Ready(self.cursor.take().map(|d| Ok(Frame::data(d))))
|
||||
}
|
||||
|
||||
fn is_end_stream(&self) -> bool {
|
||||
self.cursor.is_none()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
self.cursor.as_ref().map_or_else(
|
||||
|| SizeHint::with_exact(0),
|
||||
|c| SizeHint::with_exact(c.len() as u64),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for ReadableBody
|
||||
where
|
||||
T: Into<ReadableBodyCursor>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self {
|
||||
cursor: Some(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for ReadableBody
|
||||
where
|
||||
T: Into<ReadableBodyCursor>,
|
||||
{
|
||||
fn from(value: Option<T>) -> Self {
|
||||
Self {
|
||||
cursor: value.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for ReadableBody {
|
||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||
match value {
|
||||
LuaValue::Nil => Ok(Self::empty()),
|
||||
LuaValue::String(str) => Ok(Self::from(str)),
|
||||
LuaValue::Buffer(buf) => Ok(Self::from(buf)),
|
||||
v => Err(LuaError::FromLuaConversionError {
|
||||
from: v.type_name(),
|
||||
to: "Body".to_string(),
|
||||
message: Some(format!(
|
||||
"Invalid body - expected string or buffer, got {}",
|
||||
v.type_name()
|
||||
)),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use http_body_util::Full;
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming},
|
||||
body::Incoming,
|
||||
client::conn::http1::handshake,
|
||||
header::{HeaderValue, ACCEPT, CONTENT_LENGTH, HOST, LOCATION, USER_AGENT},
|
||||
Method, Request as HyperRequest, Response as HyperResponse, Uri,
|
||||
|
@ -9,6 +10,7 @@ use mlua::prelude::*;
|
|||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
body::ReadableBody,
|
||||
client::{http_stream::HttpStream, ws_stream::WsStream},
|
||||
shared::{
|
||||
headers::create_user_agent_header,
|
||||
|
@ -45,7 +47,7 @@ pub async fn send_request(mut request: Request, lua: Lua) -> LuaResult<Response>
|
|||
.uri()
|
||||
.to_string()
|
||||
.parse::<Url>()
|
||||
.expect("uri is valid");
|
||||
.into_lua_err()?;
|
||||
|
||||
// Some headers are required by most if not
|
||||
// all servers, make sure those are present...
|
||||
|
@ -61,7 +63,7 @@ pub async fn send_request(mut request: Request, lua: Lua) -> LuaResult<Response>
|
|||
request.inner.headers_mut().insert(USER_AGENT, ua);
|
||||
}
|
||||
if !request.headers().contains_key(CONTENT_LENGTH.as_str()) && request.method() != Method::GET {
|
||||
let len = request.inner.body().len().to_string();
|
||||
let len = request.body().len().to_string();
|
||||
let len = HeaderValue::from_str(&len).into_lua_err()?;
|
||||
request.inner.headers_mut().insert(CONTENT_LENGTH, len);
|
||||
}
|
||||
|
@ -78,18 +80,19 @@ pub async fn send_request(mut request: Request, lua: Lua) -> LuaResult<Response>
|
|||
|
||||
HyperExecutor::execute(lua.clone(), conn);
|
||||
|
||||
let incoming = sender
|
||||
.send_request(request.as_full())
|
||||
.await
|
||||
.into_lua_err()?;
|
||||
let (parts, body) = request.clone_inner().into_parts();
|
||||
let data = HyperRequest::from_parts(parts, Full::new(body.into_bytes()));
|
||||
let incoming = sender.send_request(data).await.into_lua_err()?;
|
||||
|
||||
if let Some((new_method, new_uri)) = check_redirect(&request.inner, &incoming) {
|
||||
if let Some((new_method, new_uri)) =
|
||||
check_redirect(request.inner.method().clone(), &incoming)
|
||||
{
|
||||
if request.redirects.is_some_and(|r| r >= MAX_REDIRECTS) {
|
||||
return Err(LuaError::external("Too many redirects"));
|
||||
}
|
||||
|
||||
if new_method == Method::GET {
|
||||
*request.inner.body_mut() = Bytes::new();
|
||||
*request.inner.body_mut() = ReadableBody::empty();
|
||||
}
|
||||
|
||||
*request.inner.method_mut() = new_method;
|
||||
|
@ -104,10 +107,7 @@ pub async fn send_request(mut request: Request, lua: Lua) -> LuaResult<Response>
|
|||
}
|
||||
}
|
||||
|
||||
fn check_redirect(
|
||||
request: &HyperRequest<Bytes>,
|
||||
response: &HyperResponse<Incoming>,
|
||||
) -> Option<(Method, Uri)> {
|
||||
fn check_redirect(method: Method, response: &HyperResponse<Incoming>) -> Option<(Method, Uri)> {
|
||||
if !response.status().is_redirection() {
|
||||
return None;
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ fn check_redirect(
|
|||
|
||||
let method = match response.status().as_u16() {
|
||||
301..=303 => Method::GET,
|
||||
_ => request.method().clone(),
|
||||
_ => method,
|
||||
};
|
||||
|
||||
Some((method, location))
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
use std::sync::{Arc, LazyLock};
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, LazyLock,
|
||||
};
|
||||
|
||||
use rustls::ClientConfig;
|
||||
use rustls::{crypto::ring, ClientConfig};
|
||||
|
||||
static PROVIDER_INITIALIZED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn initialize_provider() {
|
||||
if !PROVIDER_INITIALIZED.load(Ordering::Relaxed) {
|
||||
PROVIDER_INITIALIZED.store(true, Ordering::Relaxed);
|
||||
// Only errors if already installed, which is fine
|
||||
ring::default_provider().install_default().ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub static CLIENT_CONFIG: LazyLock<Arc<ClientConfig>> = LazyLock::new(|| {
|
||||
initialize_provider();
|
||||
rustls::ClientConfig::builder()
|
||||
.with_root_certificates(rustls::RootCertStore {
|
||||
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
use lune_utils::TableBuilder;
|
||||
use mlua::prelude::*;
|
||||
|
||||
pub(crate) mod body;
|
||||
pub(crate) mod client;
|
||||
pub(crate) mod server;
|
||||
pub(crate) mod shared;
|
||||
|
@ -32,6 +33,8 @@ pub fn typedefs() -> String {
|
|||
Errors when out of memory.
|
||||
*/
|
||||
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
||||
// No initial rustls setup is necessary, the respective
|
||||
// functions lazily initialize anything there as needed
|
||||
TableBuilder::new(lua)?
|
||||
.with_async_function("request", net_request)?
|
||||
.with_async_function("socket", net_socket)?
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
use std::{future::Future, net::SocketAddr, pin::Pin};
|
||||
|
||||
use async_tungstenite::{tungstenite::protocol::Role, WebSocketStream};
|
||||
use http_body_util::Full;
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming},
|
||||
service::Service as HyperService,
|
||||
Request as HyperRequest, Response as HyperResponse, StatusCode,
|
||||
body::Incoming, service::Service as HyperService, Request as HyperRequest,
|
||||
Response as HyperResponse, StatusCode,
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use mlua_luau_scheduler::{LuaSchedulerExt, LuaSpawnExt};
|
||||
|
||||
use crate::{
|
||||
body::ReadableBody,
|
||||
server::{
|
||||
config::ServeConfig,
|
||||
upgrade::{is_upgrade_request, make_upgrade_response},
|
||||
|
@ -27,7 +26,7 @@ pub(super) struct Service {
|
|||
}
|
||||
|
||||
impl HyperService<HyperRequest<Incoming>> for Service {
|
||||
type Response = HyperResponse<Full<Bytes>>;
|
||||
type Response = HyperResponse<ReadableBody>;
|
||||
type Error = LuaError;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||
|
||||
|
@ -41,7 +40,7 @@ impl HyperService<HyperRequest<Incoming>> for Service {
|
|||
Err(err) => {
|
||||
return Ok(HyperResponse::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Full::new(Bytes::from(err.to_string())))
|
||||
.body(ReadableBody::from(err.to_string()))
|
||||
.unwrap())
|
||||
}
|
||||
};
|
||||
|
@ -70,7 +69,7 @@ impl HyperService<HyperRequest<Incoming>> for Service {
|
|||
// TODO: Propagate the error somehow?
|
||||
Ok(HyperResponse::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(Full::new(Bytes::from("Lune: Internal server error")))
|
||||
.body(ReadableBody::from("Lune: Internal server error"))
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +82,7 @@ async fn handle_request(
|
|||
handler: LuaFunction,
|
||||
request: HyperRequest<Incoming>,
|
||||
address: SocketAddr,
|
||||
) -> LuaResult<HyperResponse<Full<Bytes>>> {
|
||||
) -> LuaResult<HyperResponse<ReadableBody>> {
|
||||
let request = Request::from_incoming(request, true)
|
||||
.await?
|
||||
.with_address(address);
|
||||
|
@ -97,7 +96,7 @@ async fn handle_request(
|
|||
.expect("Missing handler thread result")?;
|
||||
|
||||
let response = Response::from_lua_multi(thread_res, &lua)?;
|
||||
Ok(response.into_full())
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
async fn handle_websocket(
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use async_tungstenite::tungstenite::{error::ProtocolError, handshake::derive_accept_key};
|
||||
use http_body_util::Full;
|
||||
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming},
|
||||
body::Incoming,
|
||||
header::{HeaderName, CONNECTION, UPGRADE},
|
||||
HeaderMap, Request as HyperRequest, Response as HyperResponse, StatusCode,
|
||||
};
|
||||
|
||||
use crate::body::ReadableBody;
|
||||
|
||||
const SEC_WEBSOCKET_VERSION: HeaderName = HeaderName::from_static("sec-websocket-version");
|
||||
const SEC_WEBSOCKET_KEY: HeaderName = HeaderName::from_static("sec-websocket-key");
|
||||
const SEC_WEBSOCKET_ACCEPT: HeaderName = HeaderName::from_static("sec-websocket-accept");
|
||||
|
@ -31,7 +32,7 @@ pub fn is_upgrade_request(request: &HyperRequest<Incoming>) -> bool {
|
|||
|
||||
pub fn make_upgrade_response(
|
||||
request: &HyperRequest<Incoming>,
|
||||
) -> Result<HyperResponse<Full<Bytes>>, ProtocolError> {
|
||||
) -> Result<HyperResponse<ReadableBody>, ProtocolError> {
|
||||
let key = request
|
||||
.headers()
|
||||
.get(SEC_WEBSOCKET_KEY)
|
||||
|
@ -50,6 +51,6 @@ pub fn make_upgrade_response(
|
|||
.header(CONNECTION, "upgrade")
|
||||
.header(UPGRADE, "websocket")
|
||||
.header(SEC_WEBSOCKET_ACCEPT, derive_accept_key(key.as_bytes()))
|
||||
.body(Full::new(Bytes::from("switching to websocket protocol")))
|
||||
.body(ReadableBody::from("switching to websocket protocol"))
|
||||
.unwrap())
|
||||
}
|
||||
|
|
|
@ -1,25 +1,9 @@
|
|||
use hyper::{
|
||||
body::Bytes,
|
||||
header::{HeaderName, HeaderValue},
|
||||
HeaderMap, Method,
|
||||
};
|
||||
use mlua::prelude::*;
|
||||
|
||||
pub fn lua_value_to_bytes(value: &LuaValue) -> LuaResult<Bytes> {
|
||||
match value {
|
||||
LuaValue::Nil => Ok(Bytes::new()),
|
||||
LuaValue::Buffer(buf) => Ok(Bytes::from(buf.to_vec())),
|
||||
LuaValue::String(str) => Ok(Bytes::copy_from_slice(&str.as_bytes())),
|
||||
v => Err(LuaError::FromLuaConversionError {
|
||||
from: v.type_name(),
|
||||
to: "Bytes".to_string(),
|
||||
message: Some(format!(
|
||||
"Invalid body - expected string or buffer, got {}",
|
||||
v.type_name()
|
||||
)),
|
||||
}),
|
||||
}
|
||||
}
|
||||
use mlua::prelude::*;
|
||||
|
||||
pub fn lua_value_to_method(value: &LuaValue) -> LuaResult<Method> {
|
||||
match value {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
pub mod body;
|
||||
pub mod futures;
|
||||
pub mod headers;
|
||||
pub mod hyper;
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
use std::{collections::HashMap, net::SocketAddr};
|
||||
|
||||
use http_body_util::Full;
|
||||
use url::Url;
|
||||
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming},
|
||||
HeaderMap, Method, Request as HyperRequest,
|
||||
};
|
||||
use hyper::{body::Incoming, HeaderMap, Method, Request as HyperRequest};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::shared::{
|
||||
body::{bytes_to_full, handle_incoming_body},
|
||||
use crate::{
|
||||
body::{handle_incoming_body, ReadableBody},
|
||||
shared::{
|
||||
headers::{hash_map_to_table, header_map_to_table},
|
||||
lua::{lua_table_to_header_map, lua_value_to_bytes, lua_value_to_method},
|
||||
lua::{lua_table_to_header_map, lua_value_to_method},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -57,9 +55,7 @@ impl FromLua for RequestOptions {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Request {
|
||||
// NOTE: We use Bytes instead of Full<Bytes> to avoid
|
||||
// needing async when getting a reference to the body
|
||||
pub(crate) inner: HyperRequest<Bytes>,
|
||||
pub(crate) inner: HyperRequest<ReadableBody>,
|
||||
pub(crate) address: Option<SocketAddr>,
|
||||
pub(crate) redirects: Option<usize>,
|
||||
pub(crate) decompress: bool,
|
||||
|
@ -78,7 +74,7 @@ impl Request {
|
|||
let (body, decompress) = handle_incoming_body(&parts.headers, body, decompress).await?;
|
||||
|
||||
Ok(Self {
|
||||
inner: HyperRequest::from_parts(parts, body),
|
||||
inner: HyperRequest::from_parts(parts, ReadableBody::from(body)),
|
||||
address: None,
|
||||
redirects: None,
|
||||
decompress,
|
||||
|
@ -114,15 +110,18 @@ impl Request {
|
|||
*/
|
||||
pub fn query(&self) -> HashMap<String, Vec<String>> {
|
||||
let uri = self.inner.uri();
|
||||
let url = uri.to_string().parse::<Url>().expect("uri is valid");
|
||||
|
||||
let mut result = HashMap::<String, Vec<String>>::new();
|
||||
for (key, value) in url.query_pairs() {
|
||||
|
||||
if let Some(query) = uri.query() {
|
||||
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
|
||||
result
|
||||
.entry(key.into_owned())
|
||||
.entry(key.to_string())
|
||||
.or_default()
|
||||
.push(value.into_owned());
|
||||
.push(value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
@ -137,37 +136,23 @@ impl Request {
|
|||
Returns the body of the request.
|
||||
*/
|
||||
pub fn body(&self) -> &[u8] {
|
||||
self.inner.body()
|
||||
self.inner.body().as_slice()
|
||||
}
|
||||
|
||||
/**
|
||||
Clones the inner `hyper` request with its body
|
||||
type modified to `Full<Bytes>` for sending.
|
||||
Clones the inner `hyper` request.
|
||||
*/
|
||||
#[allow(dead_code)]
|
||||
pub fn as_full(&self) -> HyperRequest<Full<Bytes>> {
|
||||
let mut builder = HyperRequest::builder()
|
||||
.version(self.inner.version())
|
||||
.method(self.inner.method())
|
||||
.uri(self.inner.uri());
|
||||
|
||||
builder
|
||||
.headers_mut()
|
||||
.expect("request was valid")
|
||||
.extend(self.inner.headers().clone());
|
||||
|
||||
let body = bytes_to_full(self.inner.body().clone());
|
||||
builder.body(body).expect("request was valid")
|
||||
pub fn clone_inner(&self) -> HyperRequest<ReadableBody> {
|
||||
self.inner.clone()
|
||||
}
|
||||
|
||||
/**
|
||||
Takes the inner `hyper` request with its body
|
||||
type modified to `Full<Bytes>` for sending.
|
||||
Takes the inner `hyper` request by ownership.
|
||||
*/
|
||||
#[allow(dead_code)]
|
||||
pub fn into_full(self) -> HyperRequest<Full<Bytes>> {
|
||||
let (parts, body) = self.inner.into_parts();
|
||||
HyperRequest::from_parts(parts, bytes_to_full(body))
|
||||
pub fn into_inner(self) -> HyperRequest<ReadableBody> {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +164,7 @@ impl FromLua for Request {
|
|||
let uri = s.to_str()?;
|
||||
let uri = uri.parse().into_lua_err()?;
|
||||
|
||||
let mut request = HyperRequest::new(Bytes::new());
|
||||
let mut request = HyperRequest::new(ReadableBody::empty());
|
||||
*request.uri_mut() = uri;
|
||||
|
||||
Ok(Self {
|
||||
|
@ -221,8 +206,7 @@ impl FromLua for Request {
|
|||
.unwrap_or_default();
|
||||
|
||||
// Extract body
|
||||
let body = tab.get::<LuaValue>("body")?;
|
||||
let body = lua_value_to_bytes(&body)?;
|
||||
let body = tab.get::<ReadableBody>("body")?;
|
||||
|
||||
// Build the full request
|
||||
let mut request = HyperRequest::new(body);
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
use http_body_util::Full;
|
||||
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming},
|
||||
body::Incoming,
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
HeaderMap, Response as HyperResponse, StatusCode,
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::shared::{
|
||||
body::{bytes_to_full, handle_incoming_body},
|
||||
headers::header_map_to_table,
|
||||
lua::{lua_table_to_header_map, lua_value_to_bytes},
|
||||
use crate::{
|
||||
body::{handle_incoming_body, ReadableBody},
|
||||
shared::{headers::header_map_to_table, lua::lua_table_to_header_map},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Response {
|
||||
// NOTE: We use Bytes instead of Full<Bytes> to avoid
|
||||
// needing async when getting a reference to the body
|
||||
pub(crate) inner: HyperResponse<Bytes>,
|
||||
pub(crate) inner: HyperResponse<ReadableBody>,
|
||||
pub(crate) decompressed: bool,
|
||||
}
|
||||
|
||||
|
@ -35,7 +30,7 @@ impl Response {
|
|||
let (body, decompressed) = handle_incoming_body(&parts.headers, body, decompress).await?;
|
||||
|
||||
Ok(Self {
|
||||
inner: HyperResponse::from_parts(parts, body),
|
||||
inner: HyperResponse::from_parts(parts, ReadableBody::from(body)),
|
||||
decompressed,
|
||||
})
|
||||
}
|
||||
|
@ -72,42 +67,29 @@ impl Response {
|
|||
Returns the body of the response.
|
||||
*/
|
||||
pub fn body(&self) -> &[u8] {
|
||||
self.inner.body()
|
||||
self.inner.body().as_slice()
|
||||
}
|
||||
|
||||
/**
|
||||
Clones the inner `hyper` response with its body
|
||||
type modified to `Full<Bytes>` for sending.
|
||||
Clones the inner `hyper` response.
|
||||
*/
|
||||
#[allow(dead_code)]
|
||||
pub fn as_full(&self) -> HyperResponse<Full<Bytes>> {
|
||||
let mut builder = HyperResponse::builder()
|
||||
.version(self.inner.version())
|
||||
.status(self.inner.status());
|
||||
|
||||
builder
|
||||
.headers_mut()
|
||||
.expect("request was valid")
|
||||
.extend(self.inner.headers().clone());
|
||||
|
||||
let body = bytes_to_full(self.inner.body().clone());
|
||||
builder.body(body).expect("request was valid")
|
||||
pub fn clone_inner(&self) -> HyperResponse<ReadableBody> {
|
||||
self.inner.clone()
|
||||
}
|
||||
|
||||
/**
|
||||
Takes the inner `hyper` response with its body
|
||||
type modified to `Full<Bytes>` for sending.
|
||||
Takes the inner `hyper` response by ownership.
|
||||
*/
|
||||
#[allow(dead_code)]
|
||||
pub fn into_full(self) -> HyperResponse<Full<Bytes>> {
|
||||
let (parts, body) = self.inner.into_parts();
|
||||
HyperResponse::from_parts(parts, bytes_to_full(body))
|
||||
pub fn into_inner(self) -> HyperResponse<ReadableBody> {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for Response {
|
||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||
if let Ok(body) = lua_value_to_bytes(&value) {
|
||||
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
|
||||
if let Ok(body) = ReadableBody::from_lua(value.clone(), lua) {
|
||||
// String or buffer is always a 200 text/plain response
|
||||
let mut response = HyperResponse::new(body);
|
||||
response
|
||||
|
@ -130,8 +112,7 @@ impl FromLua for Response {
|
|||
.unwrap_or_default();
|
||||
|
||||
// Extract body
|
||||
let body = tab.get::<LuaValue>("body")?;
|
||||
let body = lua_value_to_bytes(&body)?;
|
||||
let body = tab.get::<ReadableBody>("body")?;
|
||||
|
||||
// Build the full response
|
||||
let mut response = HyperResponse::new(body);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std-process"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -14,11 +14,10 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
mlua = { version = "0.10.3", features = ["luau"] }
|
||||
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
|
||||
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
|
||||
|
||||
directories = "6.0"
|
||||
pin-project = "1.0"
|
||||
os_str_bytes = { version = "7.0", features = ["conversions"] }
|
||||
|
||||
bstr = "1.9"
|
||||
bytes = "1.6.0"
|
||||
|
@ -30,4 +29,4 @@ blocking = "1.6"
|
|||
futures-lite = "2.6"
|
||||
futures-util = "0.3" # Needed for select! macro...
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use std::{
|
||||
env::{
|
||||
self,
|
||||
consts::{ARCH, OS},
|
||||
},
|
||||
env::consts::{ARCH, OS},
|
||||
path::MAIN_SEPARATOR,
|
||||
process::Stdio,
|
||||
};
|
||||
|
@ -12,9 +9,11 @@ use std::{
|
|||
use mlua::prelude::*;
|
||||
use mlua_luau_scheduler::Functions;
|
||||
|
||||
use os_str_bytes::RawOsString;
|
||||
|
||||
use lune_utils::{path::get_current_dir, TableBuilder};
|
||||
use lune_utils::{
|
||||
path::get_current_dir,
|
||||
process::{ProcessArgs, ProcessEnv},
|
||||
TableBuilder,
|
||||
};
|
||||
|
||||
mod create;
|
||||
mod exec;
|
||||
|
@ -58,25 +57,15 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
|||
"little"
|
||||
})?;
|
||||
|
||||
// Create readonly args array
|
||||
let args_vec = lua
|
||||
.app_data_ref::<Vec<String>>()
|
||||
.ok_or_else(|| LuaError::runtime("Missing args vec in Lua app data"))?
|
||||
// Extract stored userdatas for args + env, the runtime struct should always provide this
|
||||
let process_args = lua
|
||||
.app_data_ref::<ProcessArgs>()
|
||||
.ok_or_else(|| LuaError::runtime("Missing process args in Lua app data"))?
|
||||
.clone();
|
||||
let process_env = lua
|
||||
.app_data_ref::<ProcessEnv>()
|
||||
.ok_or_else(|| LuaError::runtime("Missing process env in Lua app data"))?
|
||||
.clone();
|
||||
let args_tab = TableBuilder::new(lua.clone())?
|
||||
.with_sequential_values(args_vec)?
|
||||
.build_readonly()?;
|
||||
|
||||
// Create proxied table for env that gets & sets real env vars
|
||||
let env_tab = TableBuilder::new(lua.clone())?
|
||||
.with_metatable(
|
||||
TableBuilder::new(lua.clone())?
|
||||
.with_function(LuaMetaMethod::Index.name(), process_env_get)?
|
||||
.with_function(LuaMetaMethod::NewIndex.name(), process_env_set)?
|
||||
.with_function(LuaMetaMethod::Iter.name(), process_env_iter)?
|
||||
.build_readonly()?,
|
||||
)?
|
||||
.build_readonly()?;
|
||||
|
||||
// Create our process exit function, the scheduler crate provides this
|
||||
let fns = Functions::new(lua.clone())?;
|
||||
|
@ -87,73 +76,18 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
|||
.with_value("os", os)?
|
||||
.with_value("arch", arch)?
|
||||
.with_value("endianness", endianness)?
|
||||
.with_value("args", args_tab)?
|
||||
.with_value("args", process_args)?
|
||||
.with_value("cwd", cwd_str)?
|
||||
.with_value("env", env_tab)?
|
||||
.with_value("env", process_env)?
|
||||
.with_value("exit", process_exit)?
|
||||
.with_async_function("exec", process_exec)?
|
||||
.with_function("create", process_create)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn process_env_get(lua: &Lua, (_, key): (LuaValue, String)) -> LuaResult<LuaValue> {
|
||||
match env::var_os(key) {
|
||||
Some(value) => {
|
||||
let raw_value = RawOsString::new(value);
|
||||
Ok(LuaValue::String(
|
||||
lua.create_string(raw_value.to_raw_bytes())?,
|
||||
))
|
||||
}
|
||||
None => Ok(LuaValue::Nil),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_env_set(_: &Lua, (_, key, value): (LuaValue, String, Option<String>)) -> LuaResult<()> {
|
||||
// Make sure key is valid, otherwise set_var will panic
|
||||
if key.is_empty() {
|
||||
Err(LuaError::RuntimeError("Key must not be empty".to_string()))
|
||||
} else if key.contains('=') {
|
||||
Err(LuaError::RuntimeError(
|
||||
"Key must not contain the equals character '='".to_string(),
|
||||
))
|
||||
} else if key.contains('\0') {
|
||||
Err(LuaError::RuntimeError(
|
||||
"Key must not contain the NUL character".to_string(),
|
||||
))
|
||||
} else if let Some(value) = value {
|
||||
// Make sure value is valid, otherwise set_var will panic
|
||||
if value.contains('\0') {
|
||||
Err(LuaError::RuntimeError(
|
||||
"Value must not contain the NUL character".to_string(),
|
||||
))
|
||||
} else {
|
||||
env::set_var(&key, &value);
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
env::remove_var(&key);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn process_env_iter(lua: &Lua, (_, ()): (LuaValue, ())) -> LuaResult<LuaFunction> {
|
||||
let mut vars = env::vars_os().collect::<Vec<_>>().into_iter();
|
||||
lua.create_function_mut(move |lua, (): ()| match vars.next() {
|
||||
Some((key, value)) => {
|
||||
let raw_key = RawOsString::new(key);
|
||||
let raw_value = RawOsString::new(value);
|
||||
Ok((
|
||||
LuaValue::String(lua.create_string(raw_key.to_raw_bytes())?),
|
||||
LuaValue::String(lua.create_string(raw_value.to_raw_bytes())?),
|
||||
))
|
||||
}
|
||||
None => Ok((LuaValue::Nil, LuaValue::Nil)),
|
||||
})
|
||||
}
|
||||
|
||||
async fn process_exec(
|
||||
lua: Lua,
|
||||
(program, args, mut options): (String, Option<Vec<String>>, ProcessSpawnOptions),
|
||||
(program, args, mut options): (String, ProcessArgs, ProcessSpawnOptions),
|
||||
) -> LuaResult<LuaTable> {
|
||||
let stdin = options.stdio.stdin.take();
|
||||
let stdout = options.stdio.stdout;
|
||||
|
@ -171,7 +105,7 @@ async fn process_exec(
|
|||
|
||||
fn process_create(
|
||||
lua: &Lua,
|
||||
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
|
||||
(program, args, options): (String, ProcessArgs, ProcessSpawnOptions),
|
||||
) -> LuaResult<LuaValue> {
|
||||
let child = options
|
||||
.into_command(program, args)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
env::{self},
|
||||
ffi::OsString,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use lune_utils::process::ProcessArgs;
|
||||
use mlua::prelude::*;
|
||||
|
||||
use async_process::Command;
|
||||
|
@ -129,31 +131,24 @@ impl FromLua for ProcessSpawnOptions {
|
|||
}
|
||||
|
||||
impl ProcessSpawnOptions {
|
||||
pub fn into_command(self, program: impl Into<String>, args: Option<Vec<String>>) -> Command {
|
||||
let mut program = program.into();
|
||||
pub fn into_command(self, program: impl Into<OsString>, args: ProcessArgs) -> Command {
|
||||
let mut program: OsString = program.into();
|
||||
let mut args = args.into_iter().collect::<Vec<_>>();
|
||||
|
||||
// Run a shell using the command param if wanted
|
||||
let pargs = match self.shell {
|
||||
None => args,
|
||||
Some(shell) => {
|
||||
let shell_args = match args {
|
||||
Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))],
|
||||
None => vec!["-c".to_string(), program.to_string()],
|
||||
};
|
||||
program = shell.to_string();
|
||||
Some(shell_args)
|
||||
if let Some(shell) = self.shell {
|
||||
let mut shell_command = program.clone();
|
||||
for arg in args {
|
||||
shell_command.push(" ");
|
||||
shell_command.push(arg);
|
||||
}
|
||||
args = vec![OsString::from("-c"), shell_command];
|
||||
program = shell.into();
|
||||
}
|
||||
};
|
||||
|
||||
// Create command with the wanted options
|
||||
let mut cmd = match pargs {
|
||||
None => Command::new(program),
|
||||
Some(args) => {
|
||||
let mut cmd = Command::new(program);
|
||||
cmd.args(args);
|
||||
cmd
|
||||
}
|
||||
};
|
||||
|
||||
// Set dir to run in and env variables
|
||||
if let Some(cwd) = self.cwd {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std-regex"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -18,4 +18,4 @@ mlua = { version = "0.10.3", features = ["luau"] }
|
|||
regex = "1.10"
|
||||
self_cell = "1.0"
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std-roblox"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -14,10 +14,10 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
mlua = { version = "0.10.3", features = ["luau"] }
|
||||
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
|
||||
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
|
||||
|
||||
rbx_cookie = { version = "0.1.4", default-features = false }
|
||||
roblox_install = "1.0"
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-roblox = { version = "0.2.1", path = "../lune-roblox" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
lune-roblox = { version = "0.2.2", path = "../lune-roblox" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std-serde"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -42,4 +42,4 @@ sha3 = "0.10.8"
|
|||
# Check before updating it.
|
||||
blake3 = { version = "=1.5.0", features = ["traits-preview"] }
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std-stdio"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -14,7 +14,7 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
mlua = { version = "0.10.3", features = ["luau", "error-send"] }
|
||||
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
|
||||
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
|
||||
|
||||
async-io = "2.4"
|
||||
async-lock = "3.4"
|
||||
|
@ -22,4 +22,4 @@ blocking = "1.6"
|
|||
dialoguer = "0.11"
|
||||
futures-lite = "2.6"
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std-task"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -14,9 +14,9 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
mlua = { version = "0.10.3", features = ["luau"] }
|
||||
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
|
||||
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
|
||||
|
||||
async-io = "2.4"
|
||||
futures-lite = "2.6"
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-std"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -39,7 +39,7 @@ task = ["dep:lune-std-task"]
|
|||
|
||||
[dependencies]
|
||||
mlua = { version = "0.10.3", features = ["luau"] }
|
||||
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
|
||||
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
|
||||
|
||||
async-channel = "2.3"
|
||||
async-fs = "2.1"
|
||||
|
@ -48,15 +48,15 @@ async-lock = "3.4"
|
|||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
|
||||
lune-std-datetime = { optional = true, version = "0.2.1", path = "../lune-std-datetime" }
|
||||
lune-std-fs = { optional = true, version = "0.2.1", path = "../lune-std-fs" }
|
||||
lune-std-luau = { optional = true, version = "0.2.1", path = "../lune-std-luau" }
|
||||
lune-std-net = { optional = true, version = "0.2.1", path = "../lune-std-net" }
|
||||
lune-std-process = { optional = true, version = "0.2.1", path = "../lune-std-process" }
|
||||
lune-std-regex = { optional = true, version = "0.2.1", path = "../lune-std-regex" }
|
||||
lune-std-roblox = { optional = true, version = "0.2.1", path = "../lune-std-roblox" }
|
||||
lune-std-serde = { optional = true, version = "0.2.1", path = "../lune-std-serde" }
|
||||
lune-std-stdio = { optional = true, version = "0.2.1", path = "../lune-std-stdio" }
|
||||
lune-std-task = { optional = true, version = "0.2.1", path = "../lune-std-task" }
|
||||
lune-std-datetime = { optional = true, version = "0.2.2", path = "../lune-std-datetime" }
|
||||
lune-std-fs = { optional = true, version = "0.2.2", path = "../lune-std-fs" }
|
||||
lune-std-luau = { optional = true, version = "0.2.2", path = "../lune-std-luau" }
|
||||
lune-std-net = { optional = true, version = "0.2.2", path = "../lune-std-net" }
|
||||
lune-std-process = { optional = true, version = "0.2.2", path = "../lune-std-process" }
|
||||
lune-std-regex = { optional = true, version = "0.2.2", path = "../lune-std-regex" }
|
||||
lune-std-roblox = { optional = true, version = "0.2.2", path = "../lune-std-roblox" }
|
||||
lune-std-serde = { optional = true, version = "0.2.2", path = "../lune-std-serde" }
|
||||
lune-std-stdio = { optional = true, version = "0.2.2", path = "../lune-std-stdio" }
|
||||
lune-std-task = { optional = true, version = "0.2.2", path = "../lune-std-task" }
|
||||
|
|
|
@ -35,7 +35,18 @@ pub fn create(lua: Lua) -> LuaResult<LuaValue> {
|
|||
3. The lua chunk we are require-ing from
|
||||
*/
|
||||
|
||||
let require_fn = lua.create_async_function(require)?;
|
||||
let require_fn = lua.create_async_function(|lua, (source, path)| {
|
||||
// NOTE: We need to make sure that the app data reference does not
|
||||
// live through the entire require call, to prevent panicking from
|
||||
// being unable to borrow other app data in the main body of scripts
|
||||
let context = {
|
||||
let context = lua
|
||||
.app_data_ref::<RequireContext>()
|
||||
.expect("Failed to get RequireContext from app data");
|
||||
context.clone()
|
||||
};
|
||||
require(lua, context, source, path)
|
||||
})?;
|
||||
let get_source_fn = lua.create_function(move |lua, (): ()| match lua.inspect_stack(2) {
|
||||
None => Err(LuaError::runtime(
|
||||
"Failed to get stack info for require source",
|
||||
|
@ -60,7 +71,12 @@ pub fn create(lua: Lua) -> LuaResult<LuaValue> {
|
|||
.into_lua(&lua)
|
||||
}
|
||||
|
||||
async fn require(lua: Lua, (source, path): (LuaString, LuaString)) -> LuaResult<LuaMultiValue> {
|
||||
async fn require(
|
||||
lua: Lua,
|
||||
context: RequireContext,
|
||||
source: LuaString,
|
||||
path: LuaString,
|
||||
) -> LuaResult<LuaMultiValue> {
|
||||
let source = source
|
||||
.to_str()
|
||||
.into_lua_err()
|
||||
|
@ -73,11 +89,6 @@ async fn require(lua: Lua, (source, path): (LuaString, LuaString)) -> LuaResult<
|
|||
.context("Failed to parse require path as string")?
|
||||
.to_string();
|
||||
|
||||
let context = lua
|
||||
.app_data_ref::<RequireContext>()
|
||||
.expect("Failed to get RequireContext from app data")
|
||||
.clone();
|
||||
|
||||
if let Some(builtin_name) = path.strip_prefix("@lune/").map(str::to_ascii_lowercase) {
|
||||
library::require(lua, &context, &builtin_name)
|
||||
} else if let Some(self_path) = path.strip_prefix("@self/") {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune-utils"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -17,6 +17,7 @@ mlua = { version = "0.10.3", features = ["luau", "async"] }
|
|||
|
||||
console = "0.15"
|
||||
dunce = "1.0"
|
||||
os_str_bytes = { version = "7.0", features = ["conversions"] }
|
||||
path-clean = "1.0"
|
||||
pathdiff = "0.2"
|
||||
parking_lot = "0.12.3"
|
||||
|
|
|
@ -4,8 +4,13 @@ mod table_builder;
|
|||
mod version_string;
|
||||
|
||||
pub mod fmt;
|
||||
pub mod jit;
|
||||
pub mod path;
|
||||
pub mod process;
|
||||
|
||||
pub use self::table_builder::TableBuilder;
|
||||
pub use self::version_string::get_version_string;
|
||||
|
||||
// TODO: Remove this in the next major semver
|
||||
pub mod jit {
|
||||
pub use super::process::ProcessJitEnablement as JitEnablement;
|
||||
}
|
||||
|
|
252
crates/lune-utils/src/process/args.rs
Normal file
252
crates/lune-utils/src/process/args.rs
Normal file
|
@ -0,0 +1,252 @@
|
|||
#![allow(clippy::missing_panics_doc)]
|
||||
|
||||
use std::{
|
||||
env::args_os,
|
||||
ffi::OsString,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use os_str_bytes::OsStringBytes;
|
||||
|
||||
// Inner (shared) struct
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ProcessArgsInner {
|
||||
values: Vec<OsString>,
|
||||
}
|
||||
|
||||
impl FromIterator<OsString> for ProcessArgsInner {
|
||||
fn from_iter<T: IntoIterator<Item = OsString>>(iter: T) -> Self {
|
||||
Self {
|
||||
values: iter.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A struct that can be easily shared, stored in Lua app data,
|
||||
and that also guarantees the values are valid OS strings
|
||||
that can be used for process arguments.
|
||||
|
||||
Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.
|
||||
|
||||
Also provides convenience methods for working with the arguments
|
||||
as either `OsString` or `Vec<u8>`, where using the latter implicitly
|
||||
converts to an `OsString` and fails if the conversion is not possible.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessArgs {
|
||||
inner: Arc<Mutex<ProcessArgsInner>>,
|
||||
}
|
||||
|
||||
impl ProcessArgs {
|
||||
#[must_use]
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(ProcessArgsInner::default())),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn current() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(args_os().collect())),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.values.len()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.values.is_empty()
|
||||
}
|
||||
|
||||
// OS strings
|
||||
|
||||
#[must_use]
|
||||
pub fn all(&self) -> Vec<OsString> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.values.clone()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get(&self, index: usize) -> Option<OsString> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.values.get(index).cloned()
|
||||
}
|
||||
|
||||
pub fn set(&self, index: usize, val: impl Into<OsString>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if let Some(arg) = inner.values.get_mut(index) {
|
||||
*arg = val.into();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&self, val: impl Into<OsString>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.values.push(val.into());
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn pop(&self) -> Option<OsString> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.values.pop()
|
||||
}
|
||||
|
||||
pub fn insert(&self, index: usize, val: impl Into<OsString>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if index <= inner.values.len() {
|
||||
inner.values.insert(index, val.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn remove(&self, index: usize) -> Option<OsString> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if index < inner.values.len() {
|
||||
Some(inner.values.remove(index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes wrappers
|
||||
|
||||
#[must_use]
|
||||
pub fn all_bytes(&self) -> Vec<Vec<u8>> {
|
||||
self.all()
|
||||
.into_iter()
|
||||
.filter_map(OsString::into_io_vec)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_bytes(&self, index: usize) -> Option<Vec<u8>> {
|
||||
let val = self.get(index)?;
|
||||
val.into_io_vec()
|
||||
}
|
||||
|
||||
pub fn set_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {
|
||||
if let Some(val_os) = OsString::from_io_vec(val.into()) {
|
||||
self.set(index, val_os);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_bytes(&self, val: impl Into<Vec<u8>>) {
|
||||
if let Some(val_os) = OsString::from_io_vec(val.into()) {
|
||||
self.push(val_os);
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn pop_bytes(&self) -> Option<Vec<u8>> {
|
||||
self.pop().and_then(OsString::into_io_vec)
|
||||
}
|
||||
|
||||
pub fn insert_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {
|
||||
if let Some(val_os) = OsString::from_io_vec(val.into()) {
|
||||
self.insert(index, val_os);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_bytes(&self, index: usize) -> Option<Vec<u8>> {
|
||||
self.remove(index).and_then(OsString::into_io_vec)
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator implementations
|
||||
|
||||
impl IntoIterator for ProcessArgs {
|
||||
type Item = OsString;
|
||||
type IntoIter = std::vec::IntoIter<OsString>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.values.clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<OsString>> FromIterator<S> for ProcessArgs {
|
||||
fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(iter.into_iter().map(Into::into).collect())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<OsString>> Extend<S> for ProcessArgs {
|
||||
fn extend<T: IntoIterator<Item = S>>(&mut self, iter: T) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.values.extend(iter.into_iter().map(Into::into));
|
||||
}
|
||||
}
|
||||
|
||||
// Lua implementations
|
||||
|
||||
impl FromLua for ProcessArgs {
|
||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||
if let LuaValue::Nil = value {
|
||||
Ok(Self::from_iter([] as [OsString; 0]))
|
||||
} else if let LuaValue::Boolean(true) = value {
|
||||
Ok(Self::current())
|
||||
} else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {
|
||||
Ok(u.clone())
|
||||
} else if let LuaValue::Table(arr) = value {
|
||||
let mut args = Vec::new();
|
||||
for pair in arr.pairs::<LuaValue, LuaValue>() {
|
||||
let val_res = pair.map(|p| p.1.clone());
|
||||
let val = super::lua_value_to_os_string(val_res, "ProcessArgs")?;
|
||||
|
||||
super::validate_os_value(&val)?;
|
||||
|
||||
args.push(val);
|
||||
}
|
||||
Ok(Self::from_iter(args))
|
||||
} else {
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: String::from("ProcessArgs"),
|
||||
message: Some(format!(
|
||||
"Invalid type for process args - expected table or nil, got '{}'",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for ProcessArgs {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));
|
||||
methods.add_meta_method(LuaMetaMethod::Index, |_, this, index: usize| {
|
||||
if index == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(this.get(index - 1))
|
||||
}
|
||||
});
|
||||
methods.add_meta_method(LuaMetaMethod::NewIndex, |_, _, (): ()| {
|
||||
Err::<(), _>(LuaError::runtime("ProcessArgs is read-only"))
|
||||
});
|
||||
methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {
|
||||
let mut vars = this
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter_map(OsStringBytes::into_io_vec)
|
||||
.enumerate();
|
||||
lua.create_function_mut(move |lua, (): ()| match vars.next() {
|
||||
None => Ok((LuaValue::Nil, LuaValue::Nil)),
|
||||
Some((index, value)) => Ok((
|
||||
LuaValue::Integer(index as i32),
|
||||
LuaValue::String(lua.create_string(value)?),
|
||||
)),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
254
crates/lune-utils/src/process/env.rs
Normal file
254
crates/lune-utils/src/process/env.rs
Normal file
|
@ -0,0 +1,254 @@
|
|||
#![allow(clippy::missing_panics_doc)]
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
env::vars_os,
|
||||
ffi::{OsStr, OsString},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use os_str_bytes::{OsStrBytes, OsStringBytes};
|
||||
|
||||
// Inner (shared) struct
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ProcessEnvInner {
|
||||
values: BTreeMap<OsString, OsString>,
|
||||
}
|
||||
|
||||
impl FromIterator<(OsString, OsString)> for ProcessEnvInner {
|
||||
fn from_iter<T: IntoIterator<Item = (OsString, OsString)>>(iter: T) -> Self {
|
||||
Self {
|
||||
values: iter.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A struct that can be easily shared, stored in Lua app data,
|
||||
and that also guarantees the pairs are valid OS strings
|
||||
that can be used for process environment variables.
|
||||
|
||||
Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.
|
||||
|
||||
Also provides convenience methods for working with the variables
|
||||
as either `OsString` or `Vec<u8>`, where using the latter implicitly
|
||||
converts to an `OsString` and fails if the conversion is not possible.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessEnv {
|
||||
inner: Arc<Mutex<ProcessEnvInner>>,
|
||||
}
|
||||
|
||||
impl ProcessEnv {
|
||||
#[must_use]
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(ProcessEnvInner::default())),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn current() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(vars_os().collect())),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.values.len()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.values.is_empty()
|
||||
}
|
||||
|
||||
// OS strings
|
||||
|
||||
#[must_use]
|
||||
pub fn get_all(&self) -> Vec<(OsString, OsString)> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.values.clone().into_iter().collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_value(&self, key: impl AsRef<OsStr>) -> Option<OsString> {
|
||||
let key = key.as_ref();
|
||||
|
||||
super::validate_os_key(key).ok()?;
|
||||
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.values.get(key).cloned()
|
||||
}
|
||||
|
||||
pub fn set_value(&self, key: impl Into<OsString>, val: impl Into<OsString>) {
|
||||
let key = key.into();
|
||||
let val = val.into();
|
||||
|
||||
if super::validate_os_pair((&key, &val)).is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.values.insert(key, val);
|
||||
}
|
||||
|
||||
pub fn remove_value(&self, key: impl AsRef<OsStr>) {
|
||||
let key = key.as_ref();
|
||||
|
||||
if super::validate_os_key(key).is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.values.remove(key);
|
||||
}
|
||||
|
||||
// Bytes wrappers
|
||||
|
||||
#[must_use]
|
||||
pub fn get_all_bytes(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
self.get_all()
|
||||
.into_iter()
|
||||
.filter_map(|(k, v)| Some((k.into_io_vec()?, v.into_io_vec()?)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_value_bytes(&self, key: impl AsRef<[u8]>) -> Option<Vec<u8>> {
|
||||
let key = OsStr::from_io_bytes(key.as_ref())?;
|
||||
let val = self.get_value(key)?;
|
||||
val.into_io_vec()
|
||||
}
|
||||
|
||||
pub fn set_value_bytes(&self, key: impl AsRef<[u8]>, val: impl Into<Vec<u8>>) {
|
||||
let key = OsStr::from_io_bytes(key.as_ref());
|
||||
let val = OsString::from_io_vec(val.into());
|
||||
if let (Some(key), Some(val)) = (key, val) {
|
||||
self.set_value(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_value_bytes(&self, key: impl AsRef<[u8]>) {
|
||||
let key = OsStr::from_io_bytes(key.as_ref());
|
||||
if let Some(key) = key {
|
||||
self.remove_value(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator implementations
|
||||
|
||||
impl IntoIterator for ProcessEnv {
|
||||
type Item = (OsString, OsString);
|
||||
type IntoIter = std::collections::btree_map::IntoIter<OsString, OsString>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.values.clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Into<OsString>, V: Into<OsString>> FromIterator<(K, V)> for ProcessEnv {
|
||||
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(
|
||||
iter.into_iter()
|
||||
.map(|(k, v)| (k.into(), v.into()))
|
||||
.filter(|(k, v)| super::validate_os_pair((k, v)).is_ok())
|
||||
.collect(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Into<OsString>, V: Into<OsString>> Extend<(K, V)> for ProcessEnv {
|
||||
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.values.extend(
|
||||
iter.into_iter()
|
||||
.map(|(k, v)| (k.into(), v.into()))
|
||||
.filter(|(k, v)| super::validate_os_pair((k, v)).is_ok()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Lua implementations
|
||||
|
||||
impl FromLua for ProcessEnv {
|
||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||
if let LuaValue::Nil = value {
|
||||
Ok(Self::from_iter([] as [(OsString, OsString); 0]))
|
||||
} else if let LuaValue::Boolean(true) = value {
|
||||
Ok(Self::current())
|
||||
} else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {
|
||||
Ok(u.clone())
|
||||
} else if let LuaValue::Table(arr) = value {
|
||||
let mut args = Vec::new();
|
||||
for pair in arr.pairs::<LuaValue, LuaValue>() {
|
||||
let (key_res, val_res) = match pair {
|
||||
Ok((key, val)) => (Ok(key), Ok(val)),
|
||||
Err(err) => (Err(err.clone()), Err(err)),
|
||||
};
|
||||
|
||||
let key = super::lua_value_to_os_string(key_res, "ProcessEnv")?;
|
||||
let val = super::lua_value_to_os_string(val_res, "ProcessEnv")?;
|
||||
|
||||
super::validate_os_pair((&key, &val))?;
|
||||
|
||||
args.push((key, val));
|
||||
}
|
||||
Ok(Self::from_iter(args))
|
||||
} else {
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: String::from("ProcessEnv"),
|
||||
message: Some(format!(
|
||||
"Invalid type for process env - expected table or nil, got '{}'",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for ProcessEnv {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));
|
||||
methods.add_meta_method(LuaMetaMethod::Index, |_, this, key: LuaValue| {
|
||||
let key = super::lua_value_to_os_string(Ok(key), "OsString")?;
|
||||
Ok(this.get_value(key))
|
||||
});
|
||||
methods.add_meta_method(
|
||||
LuaMetaMethod::NewIndex,
|
||||
|_, this, (key, val): (LuaValue, Option<LuaValue>)| {
|
||||
let key = super::lua_value_to_os_string(Ok(key), "OsString")?;
|
||||
if let Some(val) = val {
|
||||
let val = super::lua_value_to_os_string(Ok(val), "OsString")?;
|
||||
this.set_value(key, val);
|
||||
} else {
|
||||
this.remove_value(key);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {
|
||||
let mut vars = this
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter_map(|(key, val)| Some((key.into_io_vec()?, val.into_io_vec()?)));
|
||||
lua.create_function_mut(move |lua, (): ()| match vars.next() {
|
||||
None => Ok((LuaValue::Nil, LuaValue::Nil)),
|
||||
Some((key, val)) => Ok((
|
||||
LuaValue::String(lua.create_string(key)?),
|
||||
LuaValue::String(lua.create_string(val)?),
|
||||
)),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,29 +1,31 @@
|
|||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct JitEnablement(bool);
|
||||
pub struct ProcessJitEnablement {
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl JitEnablement {
|
||||
impl ProcessJitEnablement {
|
||||
#[must_use]
|
||||
pub fn new(enabled: bool) -> Self {
|
||||
Self(enabled)
|
||||
Self { enabled }
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, enabled: bool) {
|
||||
self.0 = enabled;
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn enabled(self) -> bool {
|
||||
self.0
|
||||
self.enabled
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JitEnablement> for bool {
|
||||
fn from(val: JitEnablement) -> Self {
|
||||
impl From<ProcessJitEnablement> for bool {
|
||||
fn from(val: ProcessJitEnablement) -> Self {
|
||||
val.enabled()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for JitEnablement {
|
||||
impl From<bool> for ProcessJitEnablement {
|
||||
fn from(val: bool) -> Self {
|
||||
Self::new(val)
|
||||
}
|
78
crates/lune-utils/src/process/mod.rs
Normal file
78
crates/lune-utils/src/process/mod.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use os_str_bytes::{OsStrBytes, OsStringBytes};
|
||||
|
||||
mod args;
|
||||
mod env;
|
||||
mod jit;
|
||||
|
||||
pub use self::args::ProcessArgs;
|
||||
pub use self::env::ProcessEnv;
|
||||
pub use self::jit::ProcessJitEnablement;
|
||||
|
||||
fn lua_value_to_os_string(res: LuaResult<LuaValue>, to: &'static str) -> LuaResult<OsString> {
|
||||
let (btype, bs) = match res {
|
||||
Ok(LuaValue::String(s)) => ("string", s.as_bytes().to_vec()),
|
||||
Ok(LuaValue::Buffer(b)) => ("buffer", b.to_vec()),
|
||||
res => {
|
||||
let vtype = match res {
|
||||
Ok(v) => v.type_name(),
|
||||
Err(_) => "unknown",
|
||||
};
|
||||
return Err(LuaError::FromLuaConversionError {
|
||||
from: vtype,
|
||||
to: String::from(to),
|
||||
message: Some(format!(
|
||||
"Expected value to be a string or buffer, got '{vtype}'",
|
||||
)),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let Some(s) = OsString::from_io_vec(bs) else {
|
||||
return Err(LuaError::FromLuaConversionError {
|
||||
from: btype,
|
||||
to: String::from(to),
|
||||
message: Some(String::from("Expected {btype} to contain valid OS bytes")),
|
||||
});
|
||||
};
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn validate_os_key(key: &OsStr) -> LuaResult<()> {
|
||||
let Some(key) = key.to_io_bytes() else {
|
||||
return Err(LuaError::runtime("Key must be IO-safe"));
|
||||
};
|
||||
if key.is_empty() {
|
||||
Err(LuaError::runtime("Key must not be empty"))
|
||||
} else if key.contains(&b'=') {
|
||||
Err(LuaError::runtime(
|
||||
"Key must not contain the equals character '='",
|
||||
))
|
||||
} else if key.contains(&b'\0') {
|
||||
Err(LuaError::runtime("Key must not contain the NUL character"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_os_value(val: &OsStr) -> LuaResult<()> {
|
||||
let Some(val) = val.to_io_bytes() else {
|
||||
return Err(LuaError::runtime("Value must be IO-safe"));
|
||||
};
|
||||
if val.contains(&b'\0') {
|
||||
Err(LuaError::runtime(
|
||||
"Value must not contain the NUL character",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_os_pair((key, value): (&OsStr, &OsStr)) -> LuaResult<()> {
|
||||
validate_os_key(key)?;
|
||||
validate_os_value(value)?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "lune"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -51,7 +51,7 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
mlua = { version = "0.10.3", features = ["luau"] }
|
||||
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
|
||||
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
|
||||
|
||||
anyhow = "1.0"
|
||||
console = "0.15"
|
||||
|
@ -65,13 +65,14 @@ async-io = "2.4"
|
|||
async-fs = "2.1"
|
||||
blocking = "1.6"
|
||||
futures-lite = "2.6"
|
||||
rustls = { version = "0.23", default-features = false, features = ["std", "tls12", "ring"] }
|
||||
ureq = { version = "3.0", default-features = false, features = ["rustls", "gzip"] }
|
||||
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
lune-std = { optional = true, version = "0.2.1", path = "../lune-std" }
|
||||
lune-utils = { version = "0.2.1", path = "../lune-utils" }
|
||||
lune-std = { optional = true, version = "0.2.2", path = "../lune-std" }
|
||||
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||
|
||||
### CLI
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::{
|
|||
|
||||
use async_fs as fs;
|
||||
use blocking::unblock;
|
||||
use rustls::crypto::ring;
|
||||
|
||||
use crate::standalone::metadata::CURRENT_EXE;
|
||||
|
||||
|
@ -46,6 +47,8 @@ pub async fn get_or_download_base_executable(target: BuildTarget) -> BuildResult
|
|||
// making sure transient errors are handled gracefully and
|
||||
// with a different error message than "not found"
|
||||
let (res_status, res_body) = unblock(move || {
|
||||
// Only errors if already installed, which is fine
|
||||
ring::default_provider().install_default().ok();
|
||||
let mut res = ureq::get(release_url).call()?;
|
||||
let body = res.body_mut().read_to_vec()?;
|
||||
Ok::<_, BuildError>((res.status(), body))
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
#![allow(clippy::missing_panics_doc)]
|
||||
|
||||
use std::sync::{
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use lune_utils::jit::JitEnablement;
|
||||
use lune_utils::process::{ProcessArgs, ProcessEnv, ProcessJitEnablement};
|
||||
use mlua::prelude::*;
|
||||
use mlua_luau_scheduler::{Functions, Scheduler};
|
||||
|
||||
|
@ -58,7 +61,9 @@ impl RuntimeReturnValues {
|
|||
pub struct Runtime {
|
||||
lua: Lua,
|
||||
sched: Scheduler,
|
||||
jit: JitEnablement,
|
||||
args: ProcessArgs,
|
||||
env: ProcessEnv,
|
||||
jit: ProcessJitEnablement,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
|
@ -75,8 +80,6 @@ impl Runtime {
|
|||
pub fn new() -> LuaResult<Self> {
|
||||
let lua = Lua::new();
|
||||
|
||||
lua.set_app_data(Vec::<String>::new());
|
||||
|
||||
let sched = Scheduler::new(lua.clone());
|
||||
let fns = Functions::new(lua.clone()).expect("has scheduler");
|
||||
|
||||
|
@ -126,21 +129,47 @@ impl Runtime {
|
|||
.set(g_table.name(), g_table.create(lua.clone())?)?;
|
||||
}
|
||||
|
||||
let jit = JitEnablement::default();
|
||||
Ok(Self { lua, sched, jit })
|
||||
let args = ProcessArgs::current();
|
||||
let env = ProcessEnv::current();
|
||||
let jit = ProcessJitEnablement::default();
|
||||
|
||||
Ok(Self {
|
||||
lua,
|
||||
sched,
|
||||
args,
|
||||
env,
|
||||
jit,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Sets arguments to give in `process.args` for Lune scripts.
|
||||
|
||||
By default, `std::env::args_os()` is used.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn with_args<A, S>(self, args: A) -> Self
|
||||
pub fn with_args<A, S>(mut self, args: A) -> Self
|
||||
where
|
||||
A: IntoIterator<Item = S>,
|
||||
S: Into<String>,
|
||||
S: Into<OsString>,
|
||||
{
|
||||
let args = args.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
self.lua.set_app_data(args);
|
||||
self.args = args.into_iter().map(Into::into).collect();
|
||||
self
|
||||
}
|
||||
|
||||
/**
|
||||
Sets environment values to give in `process.env` for Lune scripts.
|
||||
|
||||
By default, `std::env::vars_os()` is used.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn with_env<E, K, V>(mut self, env: E) -> Self
|
||||
where
|
||||
E: IntoIterator<Item = (K, V)>,
|
||||
K: Into<OsString>,
|
||||
V: Into<OsString>,
|
||||
{
|
||||
self.env = env.into_iter().map(|(k, v)| (k.into(), v.into())).collect();
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -148,7 +177,10 @@ impl Runtime {
|
|||
Enables or disables JIT compilation.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn with_jit(mut self, jit_status: impl Into<JitEnablement>) -> Self {
|
||||
pub fn with_jit<J>(mut self, jit_status: J) -> Self
|
||||
where
|
||||
J: Into<ProcessJitEnablement>,
|
||||
{
|
||||
self.jit = jit_status.into();
|
||||
self
|
||||
}
|
||||
|
@ -175,8 +207,12 @@ impl Runtime {
|
|||
eprintln!("{}", RuntimeError::from(e));
|
||||
});
|
||||
|
||||
// Enable / disable the JIT as requested and store the current status as AppData
|
||||
// Store the provided args, environment variables, and jit enablement as AppData
|
||||
self.lua.set_app_data(self.args.clone());
|
||||
self.lua.set_app_data(self.env.clone());
|
||||
self.lua.set_app_data(self.jit);
|
||||
|
||||
// Enable / disable the JIT as requested, before loading anything
|
||||
self.lua.enable_jit(self.jit.enabled());
|
||||
|
||||
// Load our "main" thread
|
||||
|
|
|
@ -33,14 +33,8 @@ macro_rules! create_tests {
|
|||
let full_name = format!("{}/tests/{}.luau", workspace_dir.display(), $value);
|
||||
let script = read_to_string(&full_name).await?;
|
||||
let mut lune = Runtime::new()?
|
||||
.with_jit(true)
|
||||
.with_args(
|
||||
ARGS
|
||||
.clone()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
.with_args(ARGS.iter().cloned())
|
||||
.with_jit(true);
|
||||
let script_name = full_name
|
||||
.trim_end_matches(".luau")
|
||||
.trim_end_matches(".lua")
|
||||
|
@ -126,6 +120,7 @@ create_tests! {
|
|||
create_tests! {
|
||||
net_request_codes: "net/request/codes",
|
||||
net_request_compression: "net/request/compression",
|
||||
net_request_https: "net/request/https",
|
||||
net_request_methods: "net/request/methods",
|
||||
net_request_query: "net/request/query",
|
||||
net_request_redirect: "net/request/redirect",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "mlua-luau-scheduler"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
|
@ -18,8 +18,6 @@ workspace = true
|
|||
[dependencies]
|
||||
async-executor = "1.13"
|
||||
blocking = "1.6"
|
||||
concurrent-queue = "2.5"
|
||||
event-listener = "5.4"
|
||||
futures-lite = "2.6"
|
||||
rustc-hash = "2.1"
|
||||
tracing = "0.1"
|
||||
|
|
5
crates/mlua-luau-scheduler/src/events/mod.rs
Normal file
5
crates/mlua-luau-scheduler/src/events/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod multi;
|
||||
mod once;
|
||||
|
||||
pub(crate) use self::multi::MultiEvent;
|
||||
pub(crate) use self::once::OnceEvent;
|
91
crates/mlua-luau-scheduler/src/events/multi.rs
Normal file
91
crates/mlua-luau-scheduler/src/events/multi.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
future::Future,
|
||||
mem,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{Context, Poll, Waker},
|
||||
};
|
||||
|
||||
/**
|
||||
Internal state for events.
|
||||
*/
|
||||
#[derive(Debug, Default)]
|
||||
struct MultiEventState {
|
||||
generation: Cell<u64>,
|
||||
wakers: RefCell<Vec<Waker>>,
|
||||
}
|
||||
|
||||
/**
|
||||
A single-threaded event signal that can be notified multiple times.
|
||||
*/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct MultiEvent {
|
||||
state: Rc<MultiEventState>,
|
||||
}
|
||||
|
||||
impl MultiEvent {
|
||||
/**
|
||||
Creates a new event.
|
||||
*/
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/**
|
||||
Notifies all waiting listeners.
|
||||
*/
|
||||
pub fn notify(&self) {
|
||||
self.state.generation.set(self.state.generation.get() + 1);
|
||||
|
||||
let wakers = {
|
||||
let mut wakers = self.state.wakers.borrow_mut();
|
||||
mem::take(&mut *wakers)
|
||||
};
|
||||
|
||||
for waker in wakers {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a listener that implements `Future` and resolves when `notify` is called.
|
||||
*/
|
||||
pub fn listen(&self) -> MultiListener {
|
||||
MultiListener {
|
||||
state: self.state.clone(),
|
||||
generation: self.state.generation.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A listener future that resolves when the corresponding [`QueueEvent`] is notified.
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MultiListener {
|
||||
state: Rc<MultiEventState>,
|
||||
generation: u64,
|
||||
}
|
||||
|
||||
impl Future for MultiListener {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// Check if notify was called (generation is more recent)
|
||||
let current = self.state.generation.get();
|
||||
if current > self.generation {
|
||||
self.get_mut().generation = current;
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
||||
// No notification observed yet
|
||||
let mut wakers = self.state.wakers.borrow_mut();
|
||||
if !wakers.iter().any(|w| w.will_wake(cx.waker())) {
|
||||
wakers.push(cx.waker().clone());
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpin for MultiListener {}
|
106
crates/mlua-luau-scheduler/src/events/once.rs
Normal file
106
crates/mlua-luau-scheduler/src/events/once.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{Context, Poll, Waker},
|
||||
};
|
||||
|
||||
/**
|
||||
State which is highly optimized for a single notification event.
|
||||
|
||||
`Some` means not notified yet, `None` means notified.
|
||||
*/
|
||||
#[derive(Debug, Default)]
|
||||
struct OnceEventState {
|
||||
wakers: RefCell<Option<Vec<Waker>>>,
|
||||
}
|
||||
|
||||
impl OnceEventState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
wakers: RefCell::new(Some(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
An event that may be notified exactly once.
|
||||
|
||||
May be cheaply cloned.
|
||||
*/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OnceEvent {
|
||||
state: Rc<OnceEventState>,
|
||||
}
|
||||
|
||||
impl OnceEvent {
|
||||
/**
|
||||
Creates a new event that can be notified exactly once.
|
||||
*/
|
||||
pub fn new() -> Self {
|
||||
let initial_state = OnceEventState::new();
|
||||
Self {
|
||||
state: Rc::new(initial_state),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Notifies waiting listeners.
|
||||
|
||||
This is idempotent; subsequent calls do nothing.
|
||||
*/
|
||||
pub fn notify(&self) {
|
||||
if let Some(wakers) = { self.state.wakers.borrow_mut().take() } {
|
||||
for waker in wakers {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a listener that implements `Future` and resolves when `notify` is called.
|
||||
|
||||
If `notify` has already been called, the future will resolve immediately.
|
||||
*/
|
||||
pub fn listen(&self) -> OnceListener {
|
||||
OnceListener {
|
||||
state: self.state.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A listener that resolves when the event is notified.
|
||||
|
||||
May be cheaply cloned.
|
||||
|
||||
See [`OnceEvent`] for more information.
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub struct OnceListener {
|
||||
state: Rc<OnceEventState>,
|
||||
}
|
||||
|
||||
impl Future for OnceListener {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut wakers_guard = self.state.wakers.borrow_mut();
|
||||
match &mut *wakers_guard {
|
||||
Some(wakers) => {
|
||||
// Not yet notified
|
||||
if !wakers.iter().any(|w| w.will_wake(cx.waker())) {
|
||||
wakers.push(cx.waker().clone());
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
None => {
|
||||
// Already notified
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpin for OnceListener {}
|
|
@ -1,24 +1,24 @@
|
|||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
use event_listener::Event;
|
||||
use crate::events::OnceEvent;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Exit {
|
||||
code: Rc<Cell<Option<u8>>>,
|
||||
event: Rc<Event>,
|
||||
event: OnceEvent,
|
||||
}
|
||||
|
||||
impl Exit {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
code: Rc::new(Cell::new(None)),
|
||||
event: Rc::new(Event::new()),
|
||||
event: OnceEvent::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&self, code: u8) {
|
||||
self.code.set(Some(code));
|
||||
self.event.notify(usize::MAX);
|
||||
self.event.notify();
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Option<u8> {
|
||||
|
|
|
@ -5,8 +5,7 @@ use mlua::prelude::*;
|
|||
use crate::{
|
||||
error_callback::ThreadErrorCallback,
|
||||
queue::{DeferredThreadQueue, SpawnedThreadQueue},
|
||||
result_map::ThreadResultMap,
|
||||
thread_id::ThreadId,
|
||||
threads::{ThreadId, ThreadMap},
|
||||
traits::LuaSchedulerExt,
|
||||
util::{is_poll_pending, LuaThreadOrFunction},
|
||||
};
|
||||
|
@ -102,13 +101,13 @@ impl Functions {
|
|||
.app_data_ref::<ThreadErrorCallback>()
|
||||
.expect(ERR_METADATA_NOT_ATTACHED)
|
||||
.clone();
|
||||
let result_map = lua
|
||||
.app_data_ref::<ThreadResultMap>()
|
||||
let thread_map = lua
|
||||
.app_data_ref::<ThreadMap>()
|
||||
.expect(ERR_METADATA_NOT_ATTACHED)
|
||||
.clone();
|
||||
|
||||
let resume_queue = defer_queue.clone();
|
||||
let resume_map = result_map.clone();
|
||||
let resume_map = thread_map.clone();
|
||||
let resume =
|
||||
lua.create_function(move |lua, (thread, args): (LuaThread, LuaMultiValue)| {
|
||||
let _span = tracing::trace_span!("Scheduler::fn_resume").entered();
|
||||
|
@ -158,7 +157,7 @@ impl Functions {
|
|||
.set_environment(wrap_env)
|
||||
.into_function()?;
|
||||
|
||||
let spawn_map = result_map.clone();
|
||||
let spawn_map = thread_map.clone();
|
||||
let spawn = lua.create_function(
|
||||
move |lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {
|
||||
let _span = tracing::trace_span!("Scheduler::fn_spawn").entered();
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
mod error_callback;
|
||||
mod events;
|
||||
mod exit;
|
||||
mod functions;
|
||||
mod queue;
|
||||
mod result_map;
|
||||
mod scheduler;
|
||||
mod status;
|
||||
mod thread_id;
|
||||
mod threads;
|
||||
mod traits;
|
||||
mod util;
|
||||
|
||||
pub use functions::Functions;
|
||||
pub use scheduler::Scheduler;
|
||||
pub use status::Status;
|
||||
pub use thread_id::ThreadId;
|
||||
pub use threads::ThreadId;
|
||||
pub use traits::{IntoLuaThread, LuaSchedulerExt, LuaSpawnExt};
|
||||
|
|
|
@ -1,188 +0,0 @@
|
|||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use concurrent_queue::ConcurrentQueue;
|
||||
use event_listener::Event;
|
||||
use futures_lite::{Future, FutureExt};
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::{traits::IntoLuaThread, ThreadId};
|
||||
|
||||
/**
|
||||
Queue for storing [`LuaThread`]s with associated arguments.
|
||||
|
||||
Provides methods for pushing and draining the queue, as
|
||||
well as listening for new items being pushed to the queue.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ThreadQueue {
|
||||
inner: Rc<ThreadQueueInner>,
|
||||
}
|
||||
|
||||
impl ThreadQueue {
|
||||
pub fn new() -> Self {
|
||||
let inner = Rc::new(ThreadQueueInner::new());
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn push_item(
|
||||
&self,
|
||||
lua: &Lua,
|
||||
thread: impl IntoLuaThread,
|
||||
args: impl IntoLuaMulti,
|
||||
) -> LuaResult<ThreadId> {
|
||||
let thread = thread.into_lua_thread(lua)?;
|
||||
let args = args.into_lua_multi(lua)?;
|
||||
|
||||
tracing::trace!("pushing item to queue with {} args", args.len());
|
||||
let id = ThreadId::from(&thread);
|
||||
|
||||
let _ = self.inner.queue.push((thread, args));
|
||||
self.inner.event.notify(usize::MAX);
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drain_items(&self) -> impl Iterator<Item = (LuaThread, LuaMultiValue)> + '_ {
|
||||
self.inner.queue.try_iter()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn wait_for_item(&self) {
|
||||
if self.inner.queue.is_empty() {
|
||||
let listener = self.inner.event.listen();
|
||||
// NOTE: Need to check again, we could have gotten
|
||||
// new queued items while creating our listener
|
||||
if self.inner.queue.is_empty() {
|
||||
listener.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.queue.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SpawnedThreadQueue(ThreadQueue);
|
||||
|
||||
impl SpawnedThreadQueue {
|
||||
pub fn new() -> Self {
|
||||
Self(ThreadQueue::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SpawnedThreadQueue {
|
||||
type Target = ThreadQueue;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for SpawnedThreadQueue {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct DeferredThreadQueue(ThreadQueue);
|
||||
|
||||
impl DeferredThreadQueue {
|
||||
pub fn new() -> Self {
|
||||
Self(ThreadQueue::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DeferredThreadQueue {
|
||||
type Target = ThreadQueue;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for DeferredThreadQueue {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub type LocalBoxFuture<'fut> = Pin<Box<dyn Future<Output = ()> + 'fut>>;
|
||||
|
||||
/**
|
||||
Queue for storing local futures.
|
||||
|
||||
Provides methods for pushing and draining the queue, as
|
||||
well as listening for new items being pushed to the queue.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct FuturesQueue<'fut> {
|
||||
inner: Rc<FuturesQueueInner<'fut>>,
|
||||
}
|
||||
|
||||
impl<'fut> FuturesQueue<'fut> {
|
||||
pub fn new() -> Self {
|
||||
let inner = Rc::new(FuturesQueueInner::new());
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn push_item(&self, fut: impl Future<Output = ()> + 'fut) {
|
||||
let _ = self.inner.queue.push(fut.boxed_local());
|
||||
self.inner.event.notify(usize::MAX);
|
||||
}
|
||||
|
||||
pub fn drain_items<'outer>(
|
||||
&'outer self,
|
||||
) -> impl Iterator<Item = LocalBoxFuture<'fut>> + 'outer {
|
||||
self.inner.queue.try_iter()
|
||||
}
|
||||
|
||||
pub async fn wait_for_item(&self) {
|
||||
if self.inner.queue.is_empty() {
|
||||
self.inner.event.listen().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inner structs without ref counting so that outer structs
|
||||
// have only a single ref counter for extremely cheap clones
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ThreadQueueInner {
|
||||
queue: ConcurrentQueue<(LuaThread, LuaMultiValue)>,
|
||||
event: Event,
|
||||
}
|
||||
|
||||
impl ThreadQueueInner {
|
||||
fn new() -> Self {
|
||||
let queue = ConcurrentQueue::unbounded();
|
||||
let event = Event::new();
|
||||
Self { queue, event }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FuturesQueueInner<'fut> {
|
||||
queue: ConcurrentQueue<LocalBoxFuture<'fut>>,
|
||||
event: Event,
|
||||
}
|
||||
|
||||
impl FuturesQueueInner<'_> {
|
||||
pub fn new() -> Self {
|
||||
let queue = ConcurrentQueue::unbounded();
|
||||
let event = Event::new();
|
||||
Self { queue, event }
|
||||
}
|
||||
}
|
28
crates/mlua-luau-scheduler/src/queue/deferred.rs
Normal file
28
crates/mlua-luau-scheduler/src/queue/deferred.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use super::threads::ThreadQueue;
|
||||
|
||||
/**
|
||||
Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct DeferredThreadQueue(ThreadQueue);
|
||||
|
||||
impl DeferredThreadQueue {
|
||||
pub fn new() -> Self {
|
||||
Self(ThreadQueue::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DeferredThreadQueue {
|
||||
type Target = ThreadQueue;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for DeferredThreadQueue {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
55
crates/mlua-luau-scheduler/src/queue/futures.rs
Normal file
55
crates/mlua-luau-scheduler/src/queue/futures.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use std::{cell::RefCell, mem, pin::Pin, rc::Rc};
|
||||
|
||||
use futures_lite::prelude::*;
|
||||
|
||||
use crate::events::MultiEvent;
|
||||
|
||||
pub type LocalBoxFuture<'fut> = Pin<Box<dyn Future<Output = ()> + 'fut>>;
|
||||
|
||||
struct FuturesQueueInner<'fut> {
|
||||
queue: RefCell<Vec<LocalBoxFuture<'fut>>>,
|
||||
event: MultiEvent,
|
||||
}
|
||||
|
||||
impl FuturesQueueInner<'_> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
queue: RefCell::new(Vec::new()),
|
||||
event: MultiEvent::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Queue for storing local futures.
|
||||
|
||||
Provides methods for pushing and draining the queue, as
|
||||
well as listening for new items being pushed to the queue.
|
||||
*/
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct FuturesQueue<'fut> {
|
||||
inner: Rc<FuturesQueueInner<'fut>>,
|
||||
}
|
||||
|
||||
impl<'fut> FuturesQueue<'fut> {
|
||||
pub fn new() -> Self {
|
||||
let inner = Rc::new(FuturesQueueInner::new());
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn push_item(&self, fut: impl Future<Output = ()> + 'fut) {
|
||||
self.inner.queue.borrow_mut().push(fut.boxed_local());
|
||||
self.inner.event.notify();
|
||||
}
|
||||
|
||||
pub fn take_items(&self) -> Vec<LocalBoxFuture<'fut>> {
|
||||
let mut queue = self.inner.queue.borrow_mut();
|
||||
mem::take(&mut *queue)
|
||||
}
|
||||
|
||||
pub async fn wait_for_item(&self) {
|
||||
if self.inner.queue.borrow().is_empty() {
|
||||
self.inner.event.listen().await;
|
||||
}
|
||||
}
|
||||
}
|
8
crates/mlua-luau-scheduler/src/queue/mod.rs
Normal file
8
crates/mlua-luau-scheduler/src/queue/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
mod deferred;
|
||||
mod futures;
|
||||
mod spawned;
|
||||
mod threads;
|
||||
|
||||
pub(crate) use self::deferred::DeferredThreadQueue;
|
||||
pub(crate) use self::futures::FuturesQueue;
|
||||
pub(crate) use self::spawned::SpawnedThreadQueue;
|
28
crates/mlua-luau-scheduler/src/queue/spawned.rs
Normal file
28
crates/mlua-luau-scheduler/src/queue/spawned.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use super::threads::ThreadQueue;
|
||||
|
||||
/**
|
||||
Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SpawnedThreadQueue(ThreadQueue);
|
||||
|
||||
impl SpawnedThreadQueue {
|
||||
pub fn new() -> Self {
|
||||
Self(ThreadQueue::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SpawnedThreadQueue {
|
||||
type Target = ThreadQueue;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for SpawnedThreadQueue {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
78
crates/mlua-luau-scheduler/src/queue/threads.rs
Normal file
78
crates/mlua-luau-scheduler/src/queue/threads.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
#![allow(clippy::inline_always)]
|
||||
|
||||
use std::{cell::RefCell, mem, rc::Rc};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::{threads::ThreadId, traits::IntoLuaThread};
|
||||
|
||||
use crate::events::MultiEvent;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ThreadQueueInner {
|
||||
queue: RefCell<Vec<(LuaThread, LuaMultiValue)>>,
|
||||
event: MultiEvent,
|
||||
}
|
||||
|
||||
impl ThreadQueueInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
queue: RefCell::new(Vec::new()),
|
||||
event: MultiEvent::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Queue for storing [`LuaThread`]s with associated arguments.
|
||||
|
||||
Provides methods for pushing and draining the queue, as
|
||||
well as listening for new items being pushed to the queue.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ThreadQueue {
|
||||
inner: Rc<ThreadQueueInner>,
|
||||
}
|
||||
|
||||
impl ThreadQueue {
|
||||
pub fn new() -> Self {
|
||||
let inner = Rc::new(ThreadQueueInner::new());
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn push_item(
|
||||
&self,
|
||||
lua: &Lua,
|
||||
thread: impl IntoLuaThread,
|
||||
args: impl IntoLuaMulti,
|
||||
) -> LuaResult<ThreadId> {
|
||||
let thread = thread.into_lua_thread(lua)?;
|
||||
let args = args.into_lua_multi(lua)?;
|
||||
|
||||
tracing::trace!("pushing item to queue with {} args", args.len());
|
||||
let id = ThreadId::from(&thread);
|
||||
|
||||
self.inner.queue.borrow_mut().push((thread, args));
|
||||
self.inner.event.notify();
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn take_items(&self) -> Vec<(LuaThread, LuaMultiValue)> {
|
||||
let mut queue = self.inner.queue.borrow_mut();
|
||||
mem::take(&mut *queue)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub async fn wait_for_item(&self) {
|
||||
if self.inner.queue.borrow().is_empty() {
|
||||
self.inner.event.listen().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.queue.borrow().is_empty()
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
#![allow(clippy::inline_always)]
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use event_listener::Event;
|
||||
// NOTE: This is the hash algorithm that mlua also uses, so we
|
||||
// are not adding any additional dependencies / bloat by using it.
|
||||
use mlua::prelude::*;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::thread_id::ThreadId;
|
||||
|
||||
struct ThreadResultMapInner {
|
||||
tracked: FxHashSet<ThreadId>,
|
||||
results: FxHashMap<ThreadId, LuaResult<LuaMultiValue>>,
|
||||
events: FxHashMap<ThreadId, Rc<Event>>,
|
||||
}
|
||||
|
||||
impl ThreadResultMapInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
tracked: FxHashSet::default(),
|
||||
results: FxHashMap::default(),
|
||||
events: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ThreadResultMap {
|
||||
inner: Rc<RefCell<ThreadResultMapInner>>,
|
||||
}
|
||||
|
||||
impl ThreadResultMap {
|
||||
pub fn new() -> Self {
|
||||
let inner = Rc::new(RefCell::new(ThreadResultMapInner::new()));
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn track(&self, id: ThreadId) {
|
||||
self.inner.borrow_mut().tracked.insert(id);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_tracked(&self, id: ThreadId) -> bool {
|
||||
self.inner.borrow().tracked.contains(&id)
|
||||
}
|
||||
|
||||
pub fn insert(&self, id: ThreadId, result: LuaResult<LuaMultiValue>) {
|
||||
debug_assert!(self.is_tracked(id), "Thread must be tracked");
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
inner.results.insert(id, result);
|
||||
if let Some(event) = inner.events.remove(&id) {
|
||||
event.notify(usize::MAX);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn listen(&self, id: ThreadId) {
|
||||
debug_assert!(self.is_tracked(id), "Thread must be tracked");
|
||||
if !self.inner.borrow().results.contains_key(&id) {
|
||||
let listener = {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let event = inner
|
||||
.events
|
||||
.entry(id)
|
||||
.or_insert_with(|| Rc::new(Event::new()));
|
||||
event.listen()
|
||||
};
|
||||
listener.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let res = inner.results.remove(&id)?;
|
||||
inner.tracked.remove(&id);
|
||||
inner.events.remove(&id);
|
||||
Some(res)
|
||||
}
|
||||
}
|
|
@ -17,9 +17,8 @@ use crate::{
|
|||
error_callback::ThreadErrorCallback,
|
||||
exit::Exit,
|
||||
queue::{DeferredThreadQueue, FuturesQueue, SpawnedThreadQueue},
|
||||
result_map::ThreadResultMap,
|
||||
status::Status,
|
||||
thread_id::ThreadId,
|
||||
threads::{ThreadId, ThreadMap},
|
||||
traits::IntoLuaThread,
|
||||
util::run_until_yield,
|
||||
};
|
||||
|
@ -48,7 +47,7 @@ pub struct Scheduler {
|
|||
queue_spawn: SpawnedThreadQueue,
|
||||
queue_defer: DeferredThreadQueue,
|
||||
error_callback: ThreadErrorCallback,
|
||||
result_map: ThreadResultMap,
|
||||
thread_map: ThreadMap,
|
||||
status: Rc<Cell<Status>>,
|
||||
exit: Exit,
|
||||
}
|
||||
|
@ -68,7 +67,7 @@ impl Scheduler {
|
|||
let queue_spawn = SpawnedThreadQueue::new();
|
||||
let queue_defer = DeferredThreadQueue::new();
|
||||
let error_callback = ThreadErrorCallback::default();
|
||||
let result_map = ThreadResultMap::new();
|
||||
let result_map = ThreadMap::new();
|
||||
let exit = Exit::new();
|
||||
|
||||
assert!(
|
||||
|
@ -84,7 +83,7 @@ impl Scheduler {
|
|||
"{ERR_METADATA_ALREADY_ATTACHED}"
|
||||
);
|
||||
assert!(
|
||||
lua.app_data_ref::<ThreadResultMap>().is_none(),
|
||||
lua.app_data_ref::<ThreadMap>().is_none(),
|
||||
"{ERR_METADATA_ALREADY_ATTACHED}"
|
||||
);
|
||||
assert!(
|
||||
|
@ -105,7 +104,7 @@ impl Scheduler {
|
|||
queue_spawn,
|
||||
queue_defer,
|
||||
error_callback,
|
||||
result_map,
|
||||
thread_map: result_map,
|
||||
status,
|
||||
exit,
|
||||
}
|
||||
|
@ -201,7 +200,7 @@ impl Scheduler {
|
|||
args: impl IntoLuaMulti,
|
||||
) -> LuaResult<ThreadId> {
|
||||
let id = self.queue_spawn.push_item(&self.lua, thread, args)?;
|
||||
self.result_map.track(id);
|
||||
self.thread_map.track(id);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
|
@ -228,7 +227,7 @@ impl Scheduler {
|
|||
args: impl IntoLuaMulti,
|
||||
) -> LuaResult<ThreadId> {
|
||||
let id = self.queue_defer.push_item(&self.lua, thread, args)?;
|
||||
self.result_map.track(id);
|
||||
self.thread_map.track(id);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
|
@ -248,7 +247,7 @@ impl Scheduler {
|
|||
*/
|
||||
#[must_use]
|
||||
pub fn get_thread_result(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {
|
||||
self.result_map.remove(id)
|
||||
self.thread_map.remove(id)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -257,7 +256,7 @@ impl Scheduler {
|
|||
This will return instantly if the thread has already completed.
|
||||
*/
|
||||
pub async fn wait_for_thread(&self, id: ThreadId) {
|
||||
self.result_map.listen(id).await;
|
||||
self.thread_map.listen(id).await;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -320,7 +319,7 @@ impl Scheduler {
|
|||
when there are new Lua threads to enqueue and potentially more work to be done.
|
||||
*/
|
||||
let fut = async {
|
||||
let result_map = self.result_map.clone();
|
||||
let result_map = self.thread_map.clone();
|
||||
let process_thread = |thread: LuaThread, args| {
|
||||
// NOTE: Thread may have been cancelled from Lua
|
||||
// before we got here, so we need to check it again
|
||||
|
@ -397,21 +396,21 @@ impl Scheduler {
|
|||
let mut num_futures = 0;
|
||||
{
|
||||
let _span = trace_span!("Scheduler::drain_spawned").entered();
|
||||
for (thread, args) in self.queue_spawn.drain_items() {
|
||||
for (thread, args) in self.queue_spawn.take_items() {
|
||||
process_thread(thread, args);
|
||||
num_spawned += 1;
|
||||
}
|
||||
}
|
||||
{
|
||||
let _span = trace_span!("Scheduler::drain_deferred").entered();
|
||||
for (thread, args) in self.queue_defer.drain_items() {
|
||||
for (thread, args) in self.queue_defer.take_items() {
|
||||
process_thread(thread, args);
|
||||
num_deferred += 1;
|
||||
}
|
||||
}
|
||||
{
|
||||
let _span = trace_span!("Scheduler::drain_futures").entered();
|
||||
for fut in fut_queue.drain_items() {
|
||||
for fut in fut_queue.take_items() {
|
||||
local_exec.spawn(fut).detach();
|
||||
num_futures += 1;
|
||||
}
|
||||
|
@ -458,7 +457,7 @@ impl Drop for Scheduler {
|
|||
self.lua.remove_app_data::<SpawnedThreadQueue>();
|
||||
self.lua.remove_app_data::<DeferredThreadQueue>();
|
||||
self.lua.remove_app_data::<ThreadErrorCallback>();
|
||||
self.lua.remove_app_data::<ThreadResultMap>();
|
||||
self.lua.remove_app_data::<ThreadMap>();
|
||||
self.lua.remove_app_data::<Exit>();
|
||||
} else {
|
||||
// In any other case we panic if metadata was removed incorrectly
|
||||
|
@ -472,7 +471,7 @@ impl Drop for Scheduler {
|
|||
.remove_app_data::<ThreadErrorCallback>()
|
||||
.expect(ERR_METADATA_REMOVED);
|
||||
self.lua
|
||||
.remove_app_data::<ThreadResultMap>()
|
||||
.remove_app_data::<ThreadMap>()
|
||||
.expect(ERR_METADATA_REMOVED);
|
||||
self.lua
|
||||
.remove_app_data::<Exit>()
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use std::hash::{Hash, Hasher};
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
|
@ -12,13 +15,13 @@ use mlua::prelude::*;
|
|||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ThreadId {
|
||||
inner: usize,
|
||||
inner: *const c_void,
|
||||
}
|
||||
|
||||
impl From<&LuaThread> for ThreadId {
|
||||
fn from(thread: &LuaThread) -> Self {
|
||||
Self {
|
||||
inner: thread.to_pointer() as usize,
|
||||
inner: thread.to_pointer(),
|
||||
}
|
||||
}
|
||||
}
|
77
crates/mlua-luau-scheduler/src/threads/map.rs
Normal file
77
crates/mlua-luau-scheduler/src/threads/map.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
#![allow(clippy::inline_always)]
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use super::id::ThreadId;
|
||||
use crate::events::OnceEvent;
|
||||
|
||||
struct ThreadEvent {
|
||||
result: Option<LuaResult<LuaMultiValue>>,
|
||||
event: OnceEvent,
|
||||
}
|
||||
|
||||
impl ThreadEvent {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
result: None,
|
||||
event: OnceEvent::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ThreadMap {
|
||||
inner: Rc<RefCell<FxHashMap<ThreadId, ThreadEvent>>>,
|
||||
}
|
||||
|
||||
impl ThreadMap {
|
||||
pub fn new() -> Self {
|
||||
let inner = Rc::new(RefCell::new(FxHashMap::default()));
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn track(&self, id: ThreadId) {
|
||||
self.inner.borrow_mut().insert(id, ThreadEvent::new());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_tracked(&self, id: ThreadId) -> bool {
|
||||
self.inner.borrow().contains_key(&id)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn insert(&self, id: ThreadId, result: LuaResult<LuaMultiValue>) {
|
||||
if let Some(tracker) = self.inner.borrow_mut().get_mut(&id) {
|
||||
tracker.result.replace(result);
|
||||
tracker.event.notify();
|
||||
} else {
|
||||
panic!("Thread must be tracked");
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub async fn listen(&self, id: ThreadId) {
|
||||
if let Some(listener) = {
|
||||
let inner = self.inner.borrow();
|
||||
let tracker = inner.get(&id);
|
||||
tracker.map(|t| t.event.listen())
|
||||
} {
|
||||
listener.await;
|
||||
} else {
|
||||
panic!("Thread must be tracked");
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn remove(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {
|
||||
if let Some(mut tracker) = self.inner.borrow_mut().remove(&id) {
|
||||
tracker.result.take()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
5
crates/mlua-luau-scheduler/src/threads/mod.rs
Normal file
5
crates/mlua-luau-scheduler/src/threads/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod id;
|
||||
mod map;
|
||||
|
||||
pub use id::ThreadId;
|
||||
pub(crate) use map::ThreadMap;
|
|
@ -12,9 +12,8 @@ use tracing::trace;
|
|||
use crate::{
|
||||
exit::Exit,
|
||||
queue::{DeferredThreadQueue, FuturesQueue, SpawnedThreadQueue},
|
||||
result_map::ThreadResultMap,
|
||||
scheduler::Scheduler,
|
||||
thread_id::ThreadId,
|
||||
threads::{ThreadId, ThreadMap},
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -314,21 +313,21 @@ impl LuaSchedulerExt for Lua {
|
|||
|
||||
fn track_thread(&self, id: ThreadId) {
|
||||
let map = self
|
||||
.app_data_ref::<ThreadResultMap>()
|
||||
.app_data_ref::<ThreadMap>()
|
||||
.expect("lua threads can only be tracked from within an active scheduler");
|
||||
map.track(id);
|
||||
}
|
||||
|
||||
fn get_thread_result(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {
|
||||
let map = self
|
||||
.app_data_ref::<ThreadResultMap>()
|
||||
.app_data_ref::<ThreadMap>()
|
||||
.expect("lua threads results can only be retrieved from within an active scheduler");
|
||||
map.remove(id)
|
||||
}
|
||||
|
||||
fn wait_for_thread(&self, id: ThreadId) -> impl Future<Output = ()> {
|
||||
let map = self
|
||||
.app_data_ref::<ThreadResultMap>()
|
||||
.app_data_ref::<ThreadMap>()
|
||||
.expect("lua threads results can only be retrieved from within an active scheduler");
|
||||
async move { map.listen(id).await }
|
||||
}
|
||||
|
|
23
tests/net/request/https.luau
Normal file
23
tests/net/request/https.luau
Normal file
|
@ -0,0 +1,23 @@
|
|||
local task = require("@lune/task")
|
||||
|
||||
local util = require("./util")
|
||||
local pass = util.pass
|
||||
|
||||
-- These are some public APIs that have, or most likely have, different
|
||||
-- certificate authorities (CAs), plus are both free to use and stable.
|
||||
-- This should be enough to ensure that rustls is configured correctly.
|
||||
local servers = {
|
||||
"https://www.googleapis.com/discovery/v1/apis",
|
||||
"https://api.cloudflare.com/client/v4/ips",
|
||||
"https://azure.microsoft.com/en-us/updates/feed/",
|
||||
"https://acme-v02.api.letsencrypt.org/directory",
|
||||
"https://ip-ranges.amazonaws.com/ip-ranges.json",
|
||||
"https://en.wikipedia.org/w/api.php",
|
||||
"https://status.godaddy.com/api/v2/summary.json",
|
||||
}
|
||||
|
||||
for _, server in servers do
|
||||
task.spawn(function()
|
||||
pass("GET", server, server)
|
||||
end)
|
||||
end
|
|
@ -4,22 +4,38 @@ local stdio = require("@lune/stdio")
|
|||
local util = {}
|
||||
|
||||
function util.pass(method, url, message)
|
||||
local response = net.request({
|
||||
local success, response = pcall(net.request, {
|
||||
method = method,
|
||||
url = url,
|
||||
})
|
||||
if not response.ok then
|
||||
error(string.format("%s failed!\nResponse: %s", message, stdio.format(response)))
|
||||
if not success then
|
||||
error(`{message} errored!\nError message: {tostring(response)}`)
|
||||
elseif not response.ok then
|
||||
error(
|
||||
`{message} failed, but should have passed!`
|
||||
.. `\nStatus code: {response.statusCode}`
|
||||
.. `\nStatus message: {response.statusMessage}`
|
||||
.. `\nResponse headers: {stdio.format(response.headers)}`
|
||||
.. `\nResponse body: {response.body}`
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function util.fail(method, url, message)
|
||||
local response = net.request({
|
||||
local success, response = pcall(net.request, {
|
||||
method = method,
|
||||
url = url,
|
||||
})
|
||||
if response.ok then
|
||||
error(string.format("%s passed!\nResponse: %s", message, stdio.format(response)))
|
||||
if not success then
|
||||
error(`{message} errored!\nError message: {tostring(response)}`)
|
||||
elseif response.ok then
|
||||
error(
|
||||
`{message} passed, but should have failed!`
|
||||
.. `\nStatus code: {response.statusCode}`
|
||||
.. `\nStatus message: {response.statusMessage}`
|
||||
.. `\nResponse headers: {stdio.format(response.headers)}`
|
||||
.. `\nResponse body: {response.body}`
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@ assert(#process.args > 0, "No process arguments found")
|
|||
assert(process.args[1] == "Foo", "Invalid first argument to process")
|
||||
assert(process.args[2] == "Bar", "Invalid second argument to process")
|
||||
|
||||
local success, message = pcall(function()
|
||||
local success, err = pcall(function()
|
||||
process.args[1] = "abc"
|
||||
end)
|
||||
local message = if err == nil then nil else tostring(err)
|
||||
assert(
|
||||
success == false and type(message) == "string" and #message > 0,
|
||||
"Trying to set process arguments should throw an error with a message"
|
||||
|
|
Loading…
Add table
Reference in a new issue