Support simd_json (#105)
This PR adds support for the simd-json library whenever decoding or encoding JSON responses. This may be enabled independently of serenity and twilight support for SIMD acceleration. Co-authored-by: Kyle Simpson <kyleandrew.simpson@gmail.com>
This commit is contained in:
committed by
Kyle Simpson
parent
8cc7a22b0b
commit
cb0a74f511
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -39,6 +39,7 @@ jobs:
|
|||||||
- Windows
|
- Windows
|
||||||
- driver only
|
- driver only
|
||||||
- gateway only
|
- gateway only
|
||||||
|
- simd json
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- name: beta
|
- name: beta
|
||||||
@@ -55,6 +56,10 @@ jobs:
|
|||||||
- name: gateway only
|
- name: gateway only
|
||||||
features: serenity-rustls
|
features: serenity-rustls
|
||||||
dont-test: true
|
dont-test: true
|
||||||
|
- name: simd json
|
||||||
|
features: simd-json serenity-rustls driver gateway serenity/simd_json
|
||||||
|
rustflags: -C target-cpu=native
|
||||||
|
dont-test: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
@@ -94,6 +99,10 @@ jobs:
|
|||||||
target
|
target
|
||||||
key: ${{ runner.os }}-test-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }}
|
key: ${{ runner.os }}-test-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }}
|
||||||
|
|
||||||
|
- name: Set RUSTFLAGS
|
||||||
|
if: runner.os != 'Windows'
|
||||||
|
run: echo "RUSTFLAGS=${{ matrix.rustflags || '' }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build all features
|
- name: Build all features
|
||||||
if: matrix.features == ''
|
if: matrix.features == ''
|
||||||
run: cargo build --features full-doc
|
run: cargo build --features full-doc
|
||||||
|
|||||||
@@ -104,6 +104,11 @@ features = ["voice", "gateway"]
|
|||||||
optional = true
|
optional = true
|
||||||
version = "0.1"
|
version = "0.1"
|
||||||
|
|
||||||
|
[dependencies.simd-json]
|
||||||
|
optional = true
|
||||||
|
features = ["serde_impl"]
|
||||||
|
version = "0.6.0"
|
||||||
|
|
||||||
[dependencies.streamcatcher]
|
[dependencies.streamcatcher]
|
||||||
optional = true
|
optional = true
|
||||||
version = "1"
|
version = "1"
|
||||||
@@ -225,6 +230,8 @@ zlib-simd = ["twilight-gateway/zlib-simd"]
|
|||||||
zlib-stock = ["twilight-gateway/zlib-stock"]
|
zlib-stock = ["twilight-gateway/zlib-stock"]
|
||||||
serenity-deps = ["async-trait"]
|
serenity-deps = ["async-trait"]
|
||||||
|
|
||||||
|
simdjson = []
|
||||||
|
|
||||||
rustls-marker = []
|
rustls-marker = []
|
||||||
native-marker = []
|
native-marker = []
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ args = ["fmt", "--all"]
|
|||||||
args = ["build", "--features", "full-doc"]
|
args = ["build", "--features", "full-doc"]
|
||||||
dependencies = ["format"]
|
dependencies = ["format"]
|
||||||
|
|
||||||
|
[tasks.build-simd]
|
||||||
|
args = ["build", "--features", "full-doc,simd-json,serenity/simd_json,twilight-gateway/simd-json"]
|
||||||
|
command = "cargo"
|
||||||
|
dependencies = ["format"]
|
||||||
|
env = { "RUSTFLAGS" = "-C target-cpu=native" }
|
||||||
|
|
||||||
[tasks.build-examples]
|
[tasks.build-examples]
|
||||||
args = ["build", "--manifest-path", "./examples/Cargo.toml", "--workspace"]
|
args = ["build", "--manifest-path", "./examples/Cargo.toml", "--workspace"]
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
@@ -24,7 +30,7 @@ command = "cargo"
|
|||||||
dependencies = ["format"]
|
dependencies = ["format"]
|
||||||
|
|
||||||
[tasks.build-variants]
|
[tasks.build-variants]
|
||||||
dependencies = ["build", "build-gateway", "build-driver"]
|
dependencies = ["build", "build-gateway", "build-driver", "build-simd"]
|
||||||
|
|
||||||
[tasks.check]
|
[tasks.check]
|
||||||
args = ["check", "--features", "full-doc"]
|
args = ["check", "--features", "full-doc"]
|
||||||
@@ -37,6 +43,11 @@ dependencies = ["format"]
|
|||||||
[tasks.test]
|
[tasks.test]
|
||||||
args = ["test", "--features", "full-doc"]
|
args = ["test", "--features", "full-doc"]
|
||||||
|
|
||||||
|
[tasks.test-simd]
|
||||||
|
args = ["test", "--features", "full-doc,simd-json,serenity/simd_json,twilight-gateway/simd-json"]
|
||||||
|
command = "cargo"
|
||||||
|
env = { "RUSTFLAGS" = "-C target-cpu=native" }
|
||||||
|
|
||||||
[tasks.bench]
|
[tasks.bench]
|
||||||
description = "Runs performance benchmarks."
|
description = "Runs performance benchmarks."
|
||||||
category = "Test"
|
category = "Test"
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ The library offers:
|
|||||||
* And, by default, a fully featured voice system featuring events, queues, RT(C)P packet
|
* And, by default, a fully featured voice system featuring events, queues, RT(C)P packet
|
||||||
handling, seeking on compatible streams, shared multithreaded audio stream caches,
|
handling, seeking on compatible streams, shared multithreaded audio stream caches,
|
||||||
and direct Opus data passthrough from DCA files.
|
and direct Opus data passthrough from DCA files.
|
||||||
|
* To be able to use `simd-json` from serenity, you will need to enable the `simdjson`
|
||||||
|
feature on both songbird and serenity.
|
||||||
|
|
||||||
## Intents
|
## Intents
|
||||||
Songbird's gateway functionality requires you to specify the `GUILD_VOICE_STATES` intent.
|
Songbird's gateway functionality requires you to specify the `GUILD_VOICE_STATES` intent.
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
#[cfg(feature = "serenity")]
|
#[cfg(feature = "serenity")]
|
||||||
use futures::channel::mpsc::TrySendError;
|
use futures::channel::mpsc::TrySendError;
|
||||||
|
#[cfg(not(feature = "simd-json"))]
|
||||||
|
pub use serde_json::Error as JsonError;
|
||||||
#[cfg(feature = "serenity")]
|
#[cfg(feature = "serenity")]
|
||||||
use serenity::gateway::InterMessage;
|
use serenity::gateway::InterMessage;
|
||||||
|
#[cfg(feature = "simd-json")]
|
||||||
|
pub use simd_json::Error as JsonError;
|
||||||
#[cfg(feature = "gateway")]
|
#[cfg(feature = "gateway")]
|
||||||
use std::{error::Error, fmt};
|
use std::{error::Error, fmt};
|
||||||
#[cfg(feature = "twilight")]
|
#[cfg(feature = "twilight")]
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ impl Compressed {
|
|||||||
)?;
|
)?;
|
||||||
let mut metabytes = b"DCA1\0\0\0\0".to_vec();
|
let mut metabytes = b"DCA1\0\0\0\0".to_vec();
|
||||||
let orig_len = metabytes.len();
|
let orig_len = metabytes.len();
|
||||||
serde_json::to_writer(&mut metabytes, &metadata)?;
|
crate::json::to_writer(&mut metabytes, &metadata)?;
|
||||||
let meta_len = (metabytes.len() - orig_len)
|
let meta_len = (metabytes.len() - orig_len)
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| CodecCacheError::MetadataTooLarge)?;
|
.map_err(|_| CodecCacheError::MetadataTooLarge)?;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::input::AudioStreamError;
|
use crate::{error::JsonError, input::AudioStreamError};
|
||||||
use audiopus::error::Error as OpusError;
|
use audiopus::error::Error as OpusError;
|
||||||
use serde_json::Error as JsonError;
|
|
||||||
use std::{
|
use std::{
|
||||||
error::Error as StdError,
|
error::Error as StdError,
|
||||||
fmt::{Display, Formatter, Result as FmtResult},
|
fmt::{Display, Formatter, Result as FmtResult},
|
||||||
|
|||||||
@@ -110,9 +110,11 @@ impl FormatReader for DcaReader {
|
|||||||
return symph_err::decode_error("missing DCA1 metadata block");
|
return symph_err::decode_error("missing DCA1 metadata block");
|
||||||
}
|
}
|
||||||
|
|
||||||
let raw_json = source.read_boxed_slice_exact(size as usize)?;
|
let mut raw_json = source.read_boxed_slice_exact(size as usize)?;
|
||||||
|
|
||||||
let metadata: DcaMetadata = serde_json::from_slice::<DcaMetadata>(&raw_json)
|
// NOTE: must be mut for simd-json.
|
||||||
|
#[allow(clippy::unnecessary_mut_passed)]
|
||||||
|
let metadata: DcaMetadata = crate::json::from_slice::<DcaMetadata>(&mut raw_json)
|
||||||
.map_err(|_| SymphError::DecodeError("malformed DCA1 metadata block"))?;
|
.map_err(|_| SymphError::DecodeError("malformed DCA1 metadata block"))?;
|
||||||
|
|
||||||
let mut revision = MetadataBuilder::new();
|
let mut revision = MetadataBuilder::new();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::error::JsonError;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use symphonia_core::{meta::Metadata as ContainerMetadata, probe::ProbedMetadata};
|
use symphonia_core::{meta::Metadata as ContainerMetadata, probe::ProbedMetadata};
|
||||||
|
|
||||||
@@ -47,8 +48,8 @@ pub struct AuxMetadata {
|
|||||||
|
|
||||||
impl AuxMetadata {
|
impl AuxMetadata {
|
||||||
/// Extract metadata and details from the output of `ffprobe -of json`.
|
/// Extract metadata and details from the output of `ffprobe -of json`.
|
||||||
pub fn from_ffprobe_json(value: &[u8]) -> Result<Self, serde_json::Error> {
|
pub fn from_ffprobe_json(value: &mut [u8]) -> Result<Self, JsonError> {
|
||||||
let output: ffprobe::Output = serde_json::from_slice(value)?;
|
let output: ffprobe::Output = crate::json::from_slice(value)?;
|
||||||
|
|
||||||
Ok(output.into_aux_metadata())
|
Ok(output.into_aux_metadata())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,13 +68,13 @@ impl<P: AsRef<Path> + Send + Sync> Compose for File<P> {
|
|||||||
"-i",
|
"-i",
|
||||||
];
|
];
|
||||||
|
|
||||||
let output = Command::new("ffprobe")
|
let mut output = Command::new("ffprobe")
|
||||||
.args(&args)
|
.args(&args)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AudioStreamError::Fail(Box::new(e)))?;
|
.map_err(|e| AudioStreamError::Fail(Box::new(e)))?;
|
||||||
|
|
||||||
AuxMetadata::from_ffprobe_json(&output.stdout[..])
|
AuxMetadata::from_ffprobe_json(&mut output.stdout[..])
|
||||||
.map_err(|e| AudioStreamError::Fail(Box::new(e)))
|
.map_err(|e| AudioStreamError::Fail(Box::new(e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,13 +59,15 @@ impl YoutubeDl {
|
|||||||
async fn query(&mut self) -> Result<Output, AudioStreamError> {
|
async fn query(&mut self) -> Result<Output, AudioStreamError> {
|
||||||
let ytdl_args = ["-j", &self.url, "-f", "ba[abr>0][vcodec=none]/best"];
|
let ytdl_args = ["-j", &self.url, "-f", "ba[abr>0][vcodec=none]/best"];
|
||||||
|
|
||||||
let output = Command::new(self.program)
|
let mut output = Command::new(self.program)
|
||||||
.args(&ytdl_args)
|
.args(&ytdl_args)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AudioStreamError::Fail(Box::new(e)))?;
|
.map_err(|e| AudioStreamError::Fail(Box::new(e)))?;
|
||||||
|
|
||||||
let stdout: Output = serde_json::from_slice(&output.stdout[..])
|
// NOTE: must be mut for simd-json.
|
||||||
|
#[allow(clippy::unnecessary_mut_passed)]
|
||||||
|
let stdout: Output = crate::json::from_slice(&mut output.stdout[..])
|
||||||
.map_err(|e| AudioStreamError::Fail(Box::new(e)))?;
|
.map_err(|e| AudioStreamError::Fail(Box::new(e)))?;
|
||||||
|
|
||||||
self.metadata = Some(stdout.as_aux_metadata());
|
self.metadata = Some(stdout.as_aux_metadata());
|
||||||
|
|||||||
@@ -107,6 +107,12 @@ pub use serenity_voice_model as model;
|
|||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
pub use typemap_rev as typemap;
|
pub use typemap_rev as typemap;
|
||||||
|
|
||||||
|
// Re-export serde-json APIs locally to minimise conditional config elsewhere.
|
||||||
|
#[cfg(not(feature = "simd-json"))]
|
||||||
|
pub(crate) use serde_json as json;
|
||||||
|
#[cfg(feature = "simd-json")]
|
||||||
|
pub(crate) use simd_json::serde as json;
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
driver::Driver,
|
driver::Driver,
|
||||||
|
|||||||
10
src/ws.rs
10
src/ws.rs
@@ -1,4 +1,4 @@
|
|||||||
use crate::model::Event;
|
use crate::{error::JsonError, model::Event};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use async_tungstenite::{
|
use async_tungstenite::{
|
||||||
@@ -8,7 +8,6 @@ use async_tungstenite::{
|
|||||||
WebSocketStream,
|
WebSocketStream,
|
||||||
};
|
};
|
||||||
use futures::{SinkExt, StreamExt, TryStreamExt};
|
use futures::{SinkExt, StreamExt, TryStreamExt};
|
||||||
use serde_json::Error as JsonError;
|
|
||||||
use tokio::time::{timeout, Duration};
|
use tokio::time::{timeout, Duration};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@@ -92,7 +91,7 @@ impl ReceiverExt for WsStream {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl SenderExt for SplitSink<WsStream, Message> {
|
impl SenderExt for SplitSink<WsStream, Message> {
|
||||||
async fn send_json(&mut self, value: &Event) -> Result<()> {
|
async fn send_json(&mut self, value: &Event) -> Result<()> {
|
||||||
Ok(serde_json::to_string(value)
|
Ok(crate::json::to_string(value)
|
||||||
.map(Message::Text)
|
.map(Message::Text)
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
.map(|m| self.send(m))?
|
.map(|m| self.send(m))?
|
||||||
@@ -103,7 +102,7 @@ impl SenderExt for SplitSink<WsStream, Message> {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl SenderExt for WsStream {
|
impl SenderExt for WsStream {
|
||||||
async fn send_json(&mut self, value: &Event) -> Result<()> {
|
async fn send_json(&mut self, value: &Event) -> Result<()> {
|
||||||
Ok(serde_json::to_string(value)
|
Ok(crate::json::to_string(value)
|
||||||
.map(Message::Text)
|
.map(Message::Text)
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
.map(|m| self.send(m))?
|
.map(|m| self.send(m))?
|
||||||
@@ -114,7 +113,8 @@ impl SenderExt for WsStream {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn convert_ws_message(message: Option<Message>) -> Result<Option<Event>> {
|
pub(crate) fn convert_ws_message(message: Option<Message>) -> Result<Option<Event>> {
|
||||||
Ok(match message {
|
Ok(match message {
|
||||||
Some(Message::Text(payload)) => serde_json::from_str(&payload).map(Some)?,
|
Some(Message::Text(mut payload)) =>
|
||||||
|
crate::json::from_str(payload.as_mut_str()).map(Some)?,
|
||||||
Some(Message::Binary(bytes)) => {
|
Some(Message::Binary(bytes)) => {
|
||||||
return Err(Error::UnexpectedBinaryMessage(bytes));
|
return Err(Error::UnexpectedBinaryMessage(bytes));
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user