Skip to content

Commit

Permalink
Export split icon (#221)
Browse files Browse the repository at this point in the history
* global loader and working in progress to make plugin runtimes async

* async plugin runtime

* global loader and loader supports data url

* load icons in export split plugin in progress

* testing done

* fix clippy errors
  • Loading branch information
Pistonight authored Mar 11, 2024
1 parent bdbf08b commit ed9ef0f
Show file tree
Hide file tree
Showing 38 changed files with 1,379 additions and 223 deletions.
552 changes: 548 additions & 4 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions compiler-base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ regen-lang = "0.0.7"
tokio = { version = "1.36.0", features = [
"macros",
], optional = true}
once_cell = "1.19.0"
urlencoding = "2.1.3"

# native dependencies

Expand Down
33 changes: 33 additions & 0 deletions compiler-base/src/env/env_native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ impl<T> From<Vec<T>> for RefCounted<[T]> {
}
}

impl<T> From<Box<T>> for RefCounted<T>
where
T: ?Sized,
{
#[inline]
fn from(v: Box<T>) -> Self {
Self {
inner: Arc::from(v),
}
}
}

pub async fn yield_budget(limit: u32) {
if super::coop_tick_increment(limit) {
tokio::task::yield_now().await;
Expand All @@ -59,3 +71,24 @@ pub async fn yield_budget(limit: u32) {

/// Wait for multiple futures to complete
pub use tokio::join as join_futures;

/// Spawn futures and collect the results in a vec in the same order
pub async fn join_future_vec<TFuture>(v: Vec<TFuture>) -> Vec<Result<TFuture::Output, String>>
where
TFuture: std::future::Future + Send + 'static,
TFuture::Output: Send + 'static,
{
let len = v.len();
let mut handles = Vec::with_capacity(len);
for future in v {
handles.push(tokio::spawn(future));
}
let mut results = Vec::with_capacity(len);
for handle in handles {
match handle.await {
Ok(res) => results.push(Ok(res)),
Err(e) => results.push(Err(e.to_string())),
}
}
results
}
24 changes: 24 additions & 0 deletions compiler-base/src/env/env_wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ impl<T> From<Vec<T>> for RefCounted<[T]> {
}
}

impl<T> From<Box<T>> for RefCounted<T>
where
T: ?Sized,
{
#[inline]
fn from(v: Box<T>) -> Self {
Self { inner: Rc::from(v) }
}
}

pub async fn yield_budget(limit: u32) {
// on wasm we don't need to yield too often
// multiply the limit by 4 to reduce the number of times we need to yield
Expand Down Expand Up @@ -88,3 +98,17 @@ macro_rules! join_futures {
};
}
pub use join_futures;

/// Spawn futures and collect the results in a vec in the same order
pub async fn join_future_vec<TFuture>(v: Vec<TFuture>) -> Vec<Result<TFuture::Output, String>>
where
TFuture: std::future::Future,
{
// on wasm, since there is only one thread
// we will just run the futures sequentially
let mut results = Vec::with_capacity(v.len());
for f in v {
results.push(Ok(f.await));
}
results
}
61 changes: 42 additions & 19 deletions compiler-base/src/env/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
//! Environment setup or things that depend on the execution environment (server vs WASM).

use std::cell::RefCell;
use std::fmt::Display;
use std::ops::Deref;
use std::sync::OnceLock;

use crate::macros::late_global;
use crate::res::Loader;

#[cfg(feature = "wasm")]
pub mod env_wasm;
Expand All @@ -14,30 +17,40 @@ pub mod env_native;
#[cfg(not(feature = "wasm"))]
pub use env_native::*;

static SITE_ORIGIN: OnceLock<String> = OnceLock::new();

/// Set the site origin globally if not already set
pub fn init_site_origin(origin: String) -> Result<(), String> {
SITE_ORIGIN.set(origin)
}
/// Site origin global configuration
#[late_global(str)]
pub mod site {
#[inline]
pub fn set_origin(origin: &str) {
let _ = set(RefCounted::from(origin));
}

/// Get the site origin, or default to empty string
pub fn get_site_origin() -> &'static str {
match SITE_ORIGIN.get() {
Some(origin) => origin,
None => "",
#[inline]
pub fn get_origin() -> RefCounted<str> {
match get() {
Some(origin) => origin,
None => RefCounted::from(""),
}
}
}

/// Get the site domain (origin without url scheme)
pub fn get_site_domain() -> &'static str {
let origin = get_site_origin();
match origin.strip_prefix("https://") {
Some(domain) => domain,
None => origin.strip_prefix("http://").unwrap_or(origin),
/// Get the site domain (origin without url scheme)
pub fn get_domain() -> RefCounted<str> {
let origin = get_origin();
match origin.strip_prefix("https://") {
Some(domain) => RefCounted::from(domain),
None => match origin.strip_prefix("http://") {
Some(domain) => RefCounted::from(domain),
None => origin,
},
}
}
}

/// Global loader instance that can be used to load resources
/// outside of the usual compilation cycle. For example, in plugins
#[late_global(dyn Loader)]
pub mod global_loader {}

impl<T> Deref for RefCounted<T>
where
T: ?Sized,
Expand All @@ -50,6 +63,16 @@ where
}
}

impl<T> Display for RefCounted<T>
where
T: Display + ?Sized,
{
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
}

thread_local! {
/// Current number of ticks ran without yielding in cooperative multitasking
static COOP_TICKS: RefCell<u32> = RefCell::new(0);
Expand Down
2 changes: 1 addition & 1 deletion compiler-base/src/lang/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ where
fn into_diagnostic(self) -> DocDiagnostic {
let message = match self.help_path() {
Some(path) => {
let site_origin = env::get_site_origin();
let site_origin: &str = &env::site::get_origin();
let mut msg = format!("{self} See {site_origin}");
if !path.starts_with('/') {
msg.push('/');
Expand Down
10 changes: 3 additions & 7 deletions compiler-base/src/res/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
//! Resource resolving and loading
use std::ops::Deref;

use base64::Engine;
use serde_json::Value;

use crate::env::RefCounted;
use crate::macros::async_trait;
use crate::util;

mod path;
pub use path::*;
Expand Down Expand Up @@ -156,12 +154,10 @@ where
Some(x) if x.is_image() => x.media_type(),
_ => return Err(ResError::UnknownImageFormat(self.path.to_string())),
};
// prepare the beginning of the data url
let mut data_url = format!("data:{media_type};base64,");
// load the bytes
let bytes = self.loader.load_raw(&self.path).await?;
// encode the bytes and append to the data url
base64::engine::general_purpose::STANDARD.encode_string(bytes.deref(), &mut data_url);
// encode the bytes as a data url
let data_url = util::to_data_url_base64(media_type, &bytes);

Ok(data_url)
}
Expand Down
8 changes: 5 additions & 3 deletions compiler-base/src/res/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub enum ResPath<'u> {
/// Local path, represented as a relative path from the "root"
/// (i.e. without the leading "/")
Local(PathBuf),
/// Remote path, represented as a URL prefix (with a trailing "/")
/// Remote path, represented as a URL prefix (empty, or ends with a trailing "/")
/// and a relative path (without the leading "/")
Remote(Cow<'u, str>, PathBuf),
}
Expand Down Expand Up @@ -68,9 +68,11 @@ impl<'u> ResPath<'u> {
P: Into<PathBuf>,
{
let url = url.into();
debug_assert!(url.ends_with('/'));
debug_assert!(url.is_empty() || url.ends_with('/'));
let path = path.into();
debug_assert!(path.is_relative());
if !url.is_empty() {
debug_assert!(path.is_relative());
}

Self::Remote(url, path)
}
Expand Down
41 changes: 41 additions & 0 deletions compiler-base/src/util/data_url.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//! Conversion between data url and bytes

use std::borrow::Cow;

use base64::Engine;

pub fn to_data_url_base64(mime: &str, data: &[u8]) -> String {
let mut data_url = format!("data:{mime};base64,");
base64::engine::general_purpose::STANDARD.encode_string(data, &mut data_url);
data_url
}

#[derive(Debug, thiserror::Error)]
pub enum DataUrlParseError {
#[error("Data url should have `data:` prefix")]
InvalidPrefix,
#[error("Cannot determine data type from data url")]
InvalidType,
#[error("Cannot find data in data url")]
NoData,
#[error("Error decoding base64 from data url: {0}")]
InvalidBase64(#[from] base64::DecodeError),
}

/// Decode data url to bytes. Supports base64 and URL encoding.
pub fn bytes_from_data_url(data_url: &str) -> Result<Cow<[u8]>, DataUrlParseError> {
let data = match data_url.strip_prefix("data:") {
Some(data) => data,
None => return Err(DataUrlParseError::InvalidPrefix),
};
let mut parts = data.splitn(2, ',');
let type_and_encoding = parts.next().ok_or(DataUrlParseError::InvalidType)?;
let data = parts.next().ok_or(DataUrlParseError::NoData)?;
if type_and_encoding.ends_with(";base64") {
let data = base64::engine::general_purpose::STANDARD.decode(data)?;
return Ok(Cow::Owned(data));
}

let data = urlencoding::decode_binary(data.as_bytes());
Ok(data)
}
2 changes: 2 additions & 0 deletions compiler-base/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ mod string_map;
pub use string_map::*;
mod escape;
pub use escape::*;
mod data_url;
pub use data_url::*;

// re-exports
pub use uni_path::{Component, Path, PathBuf};
1 change: 1 addition & 0 deletions compiler-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ log = "0.4.20"
roman = "0.1.6"
flate2 = "1.0.28"
base64 = "0.21.7"
livesplit-core = "0.13.0"

[dev-dependencies]
map-macro = "0.2.6"
Expand Down
Loading

0 comments on commit ed9ef0f

Please sign in to comment.