Prepare the compiler to be async

This will allow the online editor to load imports from URL asynchroniously later

Since currently the compiler is only working on a single thread, and that we
never await on a future that could block, it is allowed to use the spin_on executor
This commit is contained in:
Olivier Goffart 2020-10-30 11:18:27 +01:00
parent a7abfea961
commit 359f42c5f7
22 changed files with 110 additions and 89 deletions

View file

@ -24,6 +24,7 @@ sixtyfps-corelib = { version = "=0.0.2", path="../../../sixtyfps_runtime/corelib
scoped-tls-hkt = "0.1"
neon = "0.5.0"
css-color-parser2 = "1.0.1"
spin_on = "0.1" #FIXME: remove and delegate to async JS instead
[build-dependencies]
neon-build = "0.5.0"

View file

@ -63,18 +63,17 @@ fn load(mut cx: FunctionContext) -> JsResult<JsValue> {
}
None => vec![],
};
let compiler_config = sixtyfps_compilerlib::CompilerConfiguration {
include_paths: &include_paths,
..Default::default()
};
let compiler_config =
sixtyfps_compilerlib::CompilerConfiguration { include_paths, ..Default::default() };
let source = std::fs::read_to_string(&path).or_else(|e| cx.throw_error(e.to_string()))?;
let (c, warnings) = match sixtyfps_interpreter::load(source, &path, compiler_config) {
(Ok(c), warnings) => (c, warnings),
(Err(()), errors) => {
errors.print();
return cx.throw_error("Compilation error");
}
};
let (c, warnings) =
match spin_on::spin_on(sixtyfps_interpreter::load(source, path.into(), compiler_config)) {
(Ok(c), warnings) => (c, warnings),
(Err(()), errors) => {
errors.print();
return cx.throw_error("Compilation error");
}
};
warnings.print();

View file

@ -15,3 +15,4 @@ path = "lib.rs"
[dependencies]
sixtyfps-compilerlib = { version = "=0.0.2", path = "../../../sixtyfps_compiler", features = ["rust", "display-diagnostics"] }
thiserror = "1"
spin_on = "0.1"

View file

@ -167,9 +167,14 @@ pub fn compile_with_config(
compiler_config.embed_resources = true;
}
};
let embed_resources = compiler_config.embed_resources;
let (doc, mut diag) =
sixtyfps_compilerlib::compile_syntax_node(syntax_node, diag, &compiler_config);
// 'spin_on' is ok here because the compiler in single threaded and does not block if there is no blocking future
let (doc, mut diag) = spin_on::spin_on(sixtyfps_compilerlib::compile_syntax_node(
syntax_node,
diag,
compiler_config,
));
if diag.has_error() {
let vec = diag.to_string_vec();
@ -207,7 +212,7 @@ pub fn compile_with_config(
write!(code_formater, "{}", generated).map_err(CompileError::SaveError)?;
println!("cargo:rerun-if-changed={}", path.display());
if !compiler_config.embed_resources {
if embed_resources {
for resource in doc.root_component.referenced_file_resources.borrow().keys() {
println!("cargo:rerun-if-changed={}", resource);
}

View file

@ -6,7 +6,7 @@
"use strict";
import * as sixtyfps from 'https://sixtyfps.io/wasm-interpreter/sixtyfps_wasm_interpreter.js';
function render_or_error(source, div) {
async function render_or_error(source, div) {
let canvas_id = 'canvas_' + Math.random().toString(36).substr(2, 9);
let canvas = document.createElement("canvas");
canvas.width = 100;
@ -14,11 +14,8 @@
canvas.id = canvas_id;
div.appendChild(canvas);
try {
sixtyfps.instantiate_from_string(source, document.location.href, canvas_id);
var compiled_component = await sixtyfps.compile_from_string(source, "");
} catch (e) {
if (e.message === "Using exceptions for control flow, don't mind me. This isn't actually an error!") {
throw e;
}
var text = document.createTextNode(e.message);
var p = document.createElement('pre');
p.appendChild(text);
@ -26,6 +23,7 @@
throw e;
}
compiled_component.run(canvas_id);
}
async function run() {

View file

@ -17,4 +17,5 @@ path = "lib.rs"
quote = "1.0"
proc-macro2 = "1.0.17"
sixtyfps-compilerlib = { version = "=0.0.2", path = "../../../sixtyfps_compiler", features = ["proc_macro_span", "rust"] }
spin_on = "0.1"

View file

@ -331,9 +331,9 @@ pub fn sixtyfps(stream: TokenStream) -> TokenStream {
let syntax_node = parser::SyntaxNodeWithSourceFile { node: syntax_node, source_file };
//println!("{:#?}", syntax_node);
let compiler_config =
CompilerConfiguration { include_paths: &include_paths, ..Default::default() };
let (root_component, mut diag) = compile_syntax_node(syntax_node, diag, &compiler_config);
let compiler_config = CompilerConfiguration { include_paths, ..Default::default() };
let (root_component, mut diag) =
spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config));
//println!("{:#?}", tree);
if diag.has_error() {
diag.map_offsets_to_span(&tokens);

View file

@ -14,7 +14,8 @@ crate-type = ["cdylib"]
[dependencies]
sixtyfps-interpreter = { path = "../../sixtyfps_runtime/interpreter" }
wasm-bindgen = { version = "0.2.67" }
wasm-bindgen = { version = "0.2.66" }
wasm-bindgen-futures = { version = "0.4.18" }
js-sys = "0.3.44"
console_error_panic_hook = { version = "0.1.6", optional = true }
wee_alloc = { version = "0.4.5", optional = true }

View file

@ -10,29 +10,25 @@ LICENSE END */
//! This wasm library can be loaded from JS to load and display the content of .60 files
#![cfg(target_arch = "wasm32")]
use std::rc::Rc;
use wasm_bindgen::prelude::*;
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
/// Compile and display the content of a string.
/// The HTML must contains a <canvas> element with the given `canvas_id`
/// where the result is gonna be rendered
/// Compile the content of a string.
///
/// Returns a promise to a compiled component which can be run with ".run()"
#[wasm_bindgen]
pub fn instantiate_from_string(
source: &str,
base_url: &str,
canvas_id: String,
) -> Result<(), JsValue> {
pub async fn compile_from_string(
source: String,
base_url: String,
) -> Result<WrappedCompiledComp, JsValue> {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
let c = match sixtyfps_interpreter::load(
source.to_owned(),
&std::path::Path::new(base_url),
Default::default(),
) {
let c = match sixtyfps_interpreter::load(source, base_url.into(), Default::default()).await {
(Ok(c), ..) => {
//TODO: warnings.print();
c
@ -84,7 +80,20 @@ pub fn instantiate_from_string(
}
};
let component = c.clone().create(canvas_id);
component.window().run(component.borrow(), component.root_item());
Ok(())
Ok(WrappedCompiledComp(c))
}
#[wasm_bindgen]
pub struct WrappedCompiledComp(Rc<sixtyfps_interpreter::ComponentDescription>);
#[wasm_bindgen]
impl WrappedCompiledComp {
/// Run this compiled component in a canvas.
/// The HTML must contains a <canvas> element with the given `canvas_id`
/// where the result is gonna be rendered
#[wasm_bindgen]
pub fn run(&self, canvas_id: String) {
let component = self.0.clone().create(canvas_id);
component.window().run(component.borrow(), component.root_item());
}
}

View file

@ -43,5 +43,6 @@ once_cell = "1"
[dev-dependencies]
regex = "1.3.7"
parser_test_macro = { path = "./parser_test_macro" }
spin_on = "0.1"

View file

@ -55,20 +55,20 @@ mod passes {
#[derive(Default)]
/// CompilationConfiguration allows configuring different aspects of the compiler.
pub struct CompilerConfiguration<'a> {
pub struct CompilerConfiguration {
/// Indicate whether to embed resources such as images in the generated output or whether
/// to retain references to the resources on the file system.
pub embed_resources: bool,
/// The compiler will look in these paths for components used in the file to compile.
pub include_paths: &'a [std::path::PathBuf],
pub include_paths: Vec<std::path::PathBuf>,
/// the name of the style. (eg: "native")
pub style: Option<&'a str>,
pub style: Option<String>,
}
pub fn compile_syntax_node(
pub async fn compile_syntax_node(
doc_node: parser::SyntaxNodeWithSourceFile,
mut diagnostics: diagnostics::FileDiagnostics,
compiler_config: &CompilerConfiguration,
compiler_config: CompilerConfiguration,
) -> (object_tree::Document, diagnostics::BuildDiagnostics) {
let mut build_diagnostics = diagnostics::BuildDiagnostics::default();
@ -80,6 +80,7 @@ pub fn compile_syntax_node(
let style = compiler_config
.style
.as_ref()
.map(Cow::from)
.or_else(|| std::env::var("SIXTYFPS_STYLE").map(Cow::from).ok())
.unwrap_or_else(|| {
@ -97,13 +98,14 @@ pub fn compile_syntax_node(
let mut all_docs = typeloader::LoadedDocuments::default();
if doc_node.source_file.is_some() {
let builtin_lib = library::widget_library().iter().find(|x| x.0 == style).map(|x| x.1);
typeloader::load_dependencies_recursively(
&doc_node,
&mut diagnostics,
&type_registry,
&global_type_registry,
&compiler_config,
library::widget_library().iter().find(|x| x.0 == style).map(|x| x.1),
builtin_lib,
&mut all_docs,
&mut build_diagnostics,
);
@ -115,7 +117,7 @@ pub fn compile_syntax_node(
if !build_diagnostics.has_error() {
// FIXME: ideally we would be able to run more passes, but currently we panic because invariant are not met.
run_passes(&doc, &mut build_diagnostics, compiler_config);
run_passes(&doc, &mut build_diagnostics, &compiler_config);
}
(doc, build_diagnostics)

View file

@ -140,14 +140,14 @@ fn process_file_source(
sixtyfps_compilerlib::parser::parse(source.clone(), Some(path));
let compile_diagnostics = if !parse_diagnostics.has_error() {
let compiler_config = sixtyfps_compilerlib::CompilerConfiguration {
style: "ugly".into(),
style: Some("ugly".into()),
..Default::default()
};
let (_, build_diags) = sixtyfps_compilerlib::compile_syntax_node(
let (_, build_diags) = spin_on::spin_on(sixtyfps_compilerlib::compile_syntax_node(
syntax_node,
parse_diagnostics,
&compiler_config,
);
compiler_config,
));
build_diags.into_iter().collect()
} else {
vec![parse_diagnostics]

View file

@ -433,20 +433,20 @@ fn rtti_for_flickable() -> (&'static str, Rc<ItemRTTI>) {
/// Create a ComponentDescription from a source.
/// The path corresponding to the source need to be passed as well (path is used for diagnostics
/// and loading relative assets)
pub fn load<'id>(
pub async fn load<'id>(
source: String,
path: &std::path::Path,
compiler_config: &CompilerConfiguration,
path: std::path::PathBuf,
compiler_config: CompilerConfiguration,
guard: generativity::Guard<'id>,
) -> (Result<Rc<ComponentDescription<'id>>, ()>, sixtyfps_compilerlib::diagnostics::BuildDiagnostics)
{
let (syntax_node, diag) = parser::parse(source, Some(path));
let (syntax_node, diag) = parser::parse(source, Some(path.as_path()));
if diag.has_error() {
let mut d = sixtyfps_compilerlib::diagnostics::BuildDiagnostics::default();
d.add(diag);
return (Err(()), d);
}
let (doc, diag) = compile_syntax_node(syntax_node, diag, &compiler_config);
let (doc, diag) = compile_syntax_node(syntax_node, diag, compiler_config).await;
if diag.has_error() {
return (Err(()), diag);
}

View file

@ -175,20 +175,21 @@ impl<'id> dynamic_component::ComponentDescription<'id> {
pub type ComponentDescription = dynamic_component::ComponentDescription<'static>;
pub type ComponentBox = dynamic_component::ComponentBox<'static>;
pub fn load(
pub async fn load(
source: String,
path: &std::path::Path,
path: std::path::PathBuf,
mut compiler_config: CompilerConfiguration,
) -> (Result<Rc<ComponentDescription>, ()>, sixtyfps_compilerlib::diagnostics::BuildDiagnostics) {
if compiler_config.style.is_none() && std::env::var("SIXTYFPS_STYLE").is_err() {
// Defaults to native if it exists:
compiler_config.style = Some(if sixtyfps_rendering_backend_default::HAS_NATIVE_STYLE {
"native"
"native".to_owned()
} else {
"ugly"
"ugly".to_owned()
});
}
dynamic_component::load(source, path, &compiler_config, unsafe {
dynamic_component::load(source, path, compiler_config, unsafe {
generativity::Guard::new(generativity::Id::new())
})
.await
}

View file

@ -19,6 +19,7 @@ scopeguard = "1.1.0"
test_driver_lib = { path = "../driver_lib" }
lazy_static = "1.4.0"
which = "4.0.2"
spin_on = "0.1"
[build-dependencies]
test_driver_lib = { path = "../driver_lib" }

View file

@ -15,13 +15,14 @@ use std::ops::Deref;
pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>> {
let source = std::fs::read_to_string(&testcase.absolute_path)?;
let include_paths = &test_driver_lib::extract_include_paths(&source)
let include_paths = test_driver_lib::extract_include_paths(&source)
.map(std::path::PathBuf::from)
.collect::<Vec<_>>();
let (syntax_node, diag) = parser::parse(source.clone(), Some(&testcase.absolute_path));
let compiler_config = CompilerConfiguration { include_paths, ..Default::default() };
let (root_component, mut diag) = compile_syntax_node(syntax_node, diag, &compiler_config);
let (root_component, mut diag) =
spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config));
if diag.has_error() {
let vec = diag.to_string_vec();

View file

@ -12,23 +12,24 @@ use std::error::Error;
pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>> {
let source = std::fs::read_to_string(&testcase.absolute_path)?;
let include_paths = &test_driver_lib::extract_include_paths(&source)
let include_paths = test_driver_lib::extract_include_paths(&source)
.map(std::path::PathBuf::from)
.collect::<Vec<_>>();
let config = sixtyfps_compilerlib::CompilerConfiguration {
include_paths: &include_paths,
..Default::default()
};
let config =
sixtyfps_compilerlib::CompilerConfiguration { include_paths, ..Default::default() };
let (component, _warnings) =
match sixtyfps_interpreter::load(source, &testcase.absolute_path, config) {
(Ok(c), diagnostics) => (c, diagnostics),
(Err(()), errors) => {
let vec = errors.to_string_vec();
errors.print();
return Err(vec.join("\n").into());
}
};
let (component, _warnings) = match spin_on::spin_on(sixtyfps_interpreter::load(
source,
testcase.absolute_path.clone(),
config,
)) {
(Ok(c), diagnostics) => (c, diagnostics),
(Err(()), errors) => {
let vec = errors.to_string_vec();
errors.print();
return Err(vec.join("\n").into());
}
};
component.create();

View file

@ -15,3 +15,4 @@ path = "main.rs"
[dependencies]
sixtyfps-compilerlib = { version = "=0.0.2", path = "../../sixtyfps_compiler", features = ["display-diagnostics", "cpp", "rust"]}
structopt = "0.3.14"
spin_on = "0.1"

View file

@ -32,8 +32,8 @@ fn main() -> std::io::Result<()> {
std::process::exit(-1);
}
let compiler_config =
CompilerConfiguration { include_paths: &args.include_paths, ..Default::default() };
let (doc, diag) = compile_syntax_node(syntax_node, diag, &compiler_config);
CompilerConfiguration { include_paths: args.include_paths, ..Default::default() };
let (doc, diag) = spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config));
let mut diag = diag.check_and_exit_on_error();

View file

@ -77,7 +77,7 @@ function update() {
}
function render_or_error(source, base_url, div) {
async function render_or_error(source, base_url, div) {
let canvas_id = 'canvas_' + Math.random().toString(36).substr(2, 9);
let canvas = document.createElement("canvas");
canvas.width = 800;
@ -86,14 +86,10 @@ function render_or_error(source, base_url, div) {
div.innerHTML = "";
div.appendChild(canvas);
try {
sixtyfps.instantiate_from_string(source, base_url, canvas_id);
var compiled_component = await sixtyfps.compile_from_string(source, base_url);
} catch (e) {
if (e.message === "Using exceptions for control flow, don't mind me. This isn't actually an error!") {
monaco.editor.setModelMarkers(editor.getModel(), "sixtyfps", []);
throw e;
}
var text = document.createTextNode(e.message);
var p = document.createElement('pre');
let text = document.createTextNode(e.message);
let p = document.createElement('pre');
p.appendChild(text);
div.innerHTML = "<pre style='color: red; background-color:#fee; margin:0'>" + p.innerHTML + "</pre>";
@ -114,6 +110,7 @@ function render_or_error(source, base_url, div) {
throw e;
}
compiled_component.run(canvas_id)
}
let keystorke_timeout_handle;

View file

@ -16,6 +16,7 @@ vtable = { version = "0.1", path="../../helper_crates/vtable" }
structopt = "0.3.14"
codemap-diagnostic = "0.1.1"
codemap = "0.1"
spin_on = "0.1"
[[bin]]

View file

@ -27,12 +27,12 @@ fn main() -> std::io::Result<()> {
let source = std::fs::read_to_string(&args.path)?;
let compiler_config = sixtyfps_compilerlib::CompilerConfiguration {
include_paths: &args.include_paths,
style: if args.style.is_empty() { None } else { Some(&args.style) },
include_paths: args.include_paths,
style: if args.style.is_empty() { None } else { Some(args.style) },
..Default::default()
};
let c = match sixtyfps_interpreter::load(source, &args.path, compiler_config) {
let c = match spin_on::spin_on(sixtyfps_interpreter::load(source, args.path, compiler_config)) {
(Ok(c), warnings) => {
warnings.print();
c