Add support for embedding resources in the rust generator

This is relatively straight-forward via a pass in the compiler to
collect the resources to embed and then use include_bytes! (once per
resource).

What's really annoying is that the rust resource enum can't store a
&'static [u8] because cbindgen doesn't represent that, probably because
the slice representation isn't guaranteed to stay as it is. So instead
this, for now, uses raw pointers.
This commit is contained in:
Simon Hausmann 2020-06-09 22:00:37 +02:00
parent 82a7c3e070
commit 1404cb73ae
15 changed files with 105 additions and 98 deletions

View file

@ -7,6 +7,10 @@ namespace sixtyfps {
union ResourceData {
SharedString absolute_file_path;
struct {
void *ptr;
size_t len;
} slice;
ResourceData() { }
~ResourceData() { }

View file

@ -127,6 +127,7 @@ fn to_js_value<'cx>(val: interpreter::Value, cx: &mut impl Context<'cx>) -> Hand
Value::Resource(r) => match r {
Resource::None => JsUndefined::new().as_value(cx),
Resource::AbsoluteFilePath(path) => JsString::new(cx, path.as_str()).as_value(cx),
Resource::EmbeddedData { .. } => JsNull::new().as_value(cx), // TODO: maybe pass around node buffers?
},
}
}

View file

@ -53,7 +53,15 @@ pub fn compile(path: impl AsRef<std::path::Path>) -> Result<(), CompileError> {
let mut tr = typeregister::TypeRegister::builtin();
let doc = object_tree::Document::from_node(syntax_node, &mut diag, &mut tr);
run_passes(&doc, &mut diag, &mut tr);
let mut compiler_config = CompilerConfiguration::default();
if let Some(target) = env::var("TARGET").ok() {
if target == "wasm32-unknown-unknown" {
compiler_config.embed_resources = true;
}
};
run_passes(&doc, &mut diag, &mut tr, &compiler_config);
if diag.has_error() {
let vec = diag.inner.iter().map(|d| d.message.clone()).collect();

View file

@ -139,7 +139,8 @@ pub fn sixtyfps(stream: TokenStream) -> TokenStream {
//println!("{:#?}", syntax_node);
let mut tr = typeregister::TypeRegister::builtin();
let tree = object_tree::Document::from_node(syntax_node, &mut diag, &mut tr);
run_passes(&tree, &mut diag, &mut tr);
let compiler_config = CompilerConfiguration::default();
run_passes(&tree, &mut diag, &mut tr, &compiler_config);
//println!("{:#?}", tree);
if diag.has_error() {
diag.map_offsets_to_span(&tokens);

View file

@ -1,3 +1,3 @@
fn main() {
sixtyfps_build::compile("src/hello.60").unwrap();
sixtyfps_build::compile("../cpptest/hello.60").unwrap();
}

View file

@ -1,84 +0,0 @@
component TwoRectangle := Rectangle {
signal clicked;
Rectangle {
x: 50;
y: 50.;
width: 25;
height: 25;
color: red;
my_area := TouchArea {
width: 25;
height: 25;
clicked => { root.clicked() }
}
}
}
component ButtonRectangle := Rectangle {
property<string> button_text;
signal clicked;
width: 100;
height: 75;
TouchArea {
width: 100;
height: 75;
clicked => { root.clicked() }
}
Text {
x: 50;
y: 10;
text: button_text;
color: black;
}
}
Hello := Rectangle {
signal foobar;
property<int32> counter;
color: white;
TwoRectangle {
width: 100;
height: 100;
color: blue;
clicked => { foobar() }
}
Rectangle {
x: 100;
y: 100;
width: (100);
height: {100}
color: green;
Rectangle {
x: 50;
y: 50.;
width: 25;
height: 25;
color: yellow;
}
}
ButtonRectangle {
color: 4289374890;
x: 50;
y: 225;
clicked => { counter += 1 }
button_text: "+";
}
counter_label := Text { x: 100; y: 300; text: counter; color: black; }
ButtonRectangle {
color: 4289374890;
x: 50;
y: 350;
clicked => { counter -= 1 }
button_text: "-";
}
}

View file

@ -84,7 +84,7 @@ pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option<TokenSt
quote!(#field_name.)
};
let rust_property = quote!(#rust_property_accessor_prefix#rust_property_ident);
let tokens_for_expression = compile_expression(binding_expression);
let tokens_for_expression = compile_expression(binding_expression, &component);
if matches!(item.lookup_property(k.as_str()), Type::Signal) {
init.push(quote!(
@ -114,7 +114,19 @@ pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option<TokenSt
let item_tree_array_len = item_tree_array.len();
let resource_symbols: Vec<proc_macro2::TokenStream> = component
.embedded_file_resources
.borrow()
.iter()
.map(|(path, id)| {
let symbol = quote::format_ident!("SFPS_EMBEDDED_RESOURCE_{}", id);
quote!(const #symbol: &'static [u8] = std::include_bytes!(#path);)
})
.collect();
Some(quote!(
#(#resource_symbols)*
use sixtyfps::re_exports::const_field_offset;
#[derive(sixtyfps::re_exports::FieldOffsets)]
#[repr(C)]
@ -169,12 +181,12 @@ fn access_member(element: &Rc<RefCell<Element>>, name: &str) -> TokenStream {
}
}
fn compile_expression(e: &Expression) -> TokenStream {
fn compile_expression(e: &Expression, component: &Component) -> TokenStream {
match e {
Expression::StringLiteral(s) => quote!(sixtyfps::re_exports::SharedString::from(#s)),
Expression::NumberLiteral(n) => quote!(#n as _),
Expression::Cast { from, to } => {
let f = compile_expression(&*from);
let f = compile_expression(&*from, &component);
match (from.ty(), to) {
(Type::Float32, Type::String) | (Type::Int32, Type::String) => {
quote!(sixtyfps::re_exports::SharedString::from(format!("{}", #f).as_str()))
@ -187,7 +199,7 @@ fn compile_expression(e: &Expression) -> TokenStream {
quote!(_self.#access.get(context))
}
Expression::CodeBlock(sub) => {
let map = sub.iter().map(|e| compile_expression(e));
let map = sub.iter().map(|e| compile_expression(e, &component));
quote!({ #(#map);* })
}
Expression::SignalReference { element, name, .. } => {
@ -196,7 +208,7 @@ fn compile_expression(e: &Expression) -> TokenStream {
}
Expression::FunctionCall { function } => {
if matches!(function.ty(), Type::Signal) {
let base = compile_expression(function);
let base = compile_expression(function, &component);
quote!(#base.emit(&context, ()))
} else {
let error = format!("the function {:?} is not a signal", e);
@ -206,15 +218,20 @@ fn compile_expression(e: &Expression) -> TokenStream {
Expression::SelfAssignement { lhs, rhs, op } => match &**lhs {
Expression::PropertyReference { element, name, .. } => {
let lhs = access_member(&element.upgrade().unwrap(), name.as_str());
let rhs = compile_expression(&*rhs);
let rhs = compile_expression(&*rhs, &component);
let op = proc_macro2::Punct::new(*op, proc_macro2::Spacing::Alone);
quote!( _self.#lhs.set(_self.#lhs.get(context) #op &(#rhs) ))
}
_ => panic!("typechecking should make sure this was a PropertyReference"),
},
Expression::ResourceReference { absolute_source_path } => {
if let Some(id) = component.embedded_file_resources.borrow().get(absolute_source_path) {
let symbol = quote::format_ident!("SFPS_EMBEDDED_RESOURCE_{}", id);
quote!(sixtyfps::re_exports::Resource::EmbeddedData{ptr: #symbol.as_ptr(), len: #symbol.len()})
} else {
quote!(sixtyfps::re_exports::Resource::AbsoluteFilePath(sixtyfps::re_exports::SharedString::from(#absolute_source_path)))
}
}
_ => {
let error = format!("unsupported expression {:?}", e);
quote!(compile_error! {#error})

View file

@ -19,6 +19,7 @@ pub mod parser;
pub mod typeregister;
mod passes {
pub mod collect_resources;
pub mod inlining;
pub mod lower_layout;
pub mod move_declarations;
@ -26,14 +27,23 @@ mod passes {
pub mod unique_id;
}
#[derive(Default)]
pub struct CompilerConfiguration {
pub embed_resources: bool,
}
pub fn run_passes(
doc: &object_tree::Document,
diag: &mut diagnostics::Diagnostics,
tr: &mut typeregister::TypeRegister,
compiler_config: &CompilerConfiguration,
) {
passes::resolving::resolve_expressions(doc, diag, tr);
passes::inlining::inline(doc);
passes::lower_layout::lower_layouts(&doc.root_component, diag);
passes::unique_id::assign_unique_id(&doc.root_component);
passes::move_declarations::move_declarations(&doc.root_component);
if compiler_config.embed_resources {
passes::collect_resources::collect_resources(&doc.root_component);
}
}

View file

@ -50,6 +50,11 @@ pub struct Component {
/// List of elements that are not attached to the root anymore because they have been
/// optimized away, but their properties may still be in use
pub optimized_elements: RefCell<Vec<Rc<RefCell<Element>>>>,
/// Map of resources to embed in the generated binary, indexed by their absolute path on
/// disk on the build system and valued by a unique integer id, that can be used by the
/// generator for symbol generation.
pub embedded_file_resources: RefCell<HashMap<String, usize>>,
}
impl Component {

View file

@ -0,0 +1,30 @@
use crate::expression_tree::Expression;
use crate::object_tree::*;
use std::cell::RefCell;
use std::rc::Rc;
pub fn collect_resources(component: &Rc<Component>) {
fn collect_resources_recursively(elem: &Rc<RefCell<Element>>, component: &Rc<Component>) {
for e in &elem.borrow().children {
collect_resources_recursively(e, component)
}
let bindings = &elem.borrow().bindings;
for e in bindings.values() {
fn collect_resources(e: &Expression, component: &Rc<Component>) {
match e {
Expression::ResourceReference { absolute_source_path } => {
let mut resources = component.embedded_file_resources.borrow_mut();
let maybe_id = resources.len();
resources.entry(absolute_source_path.clone()).or_insert(maybe_id);
}
_ => {}
};
e.visit(|e| collect_resources(e, component));
}
collect_resources(&e, component);
}
}
collect_resources_recursively(&component.root_element, component)
}

View file

@ -208,12 +208,19 @@ impl Color {
/// system or embedded in the resulting binary. Or they might be URLs to a web server and a downloaded
/// is necessary before they can be used.
#[derive(Clone, PartialEq, Debug)]
#[repr(C)]
#[repr(u8)]
pub enum Resource {
/// A resource that does not represent any data.
None,
/// A resource that points to a file in the file system
AbsoluteFilePath(crate::SharedString),
/// A resource that is embedded in the program and accessible via pointer
EmbeddedData {
/// raw pointer to the read-only data
ptr: *const u8,
/// size of the embedded data
len: usize,
},
}
impl Default for Resource {

View file

@ -92,7 +92,8 @@ pub fn load(
if !diag.inner.is_empty() {
return Err(diag);
}
run_passes(&tree, &mut diag, &mut tr);
let compiler_config = CompilerConfiguration::default();
run_passes(&tree, &mut diag, &mut tr, &compiler_config);
if !diag.inner.is_empty() {
return Err(diag);
}

View file

@ -314,6 +314,11 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder {
let image = image::open(image_path.as_path()).unwrap().into_rgba();
Some(self.create_image(image))
}
Resource::EmbeddedData { ptr, len } => {
let image_slice = unsafe { std::slice::from_raw_parts(*ptr, *len) };
let image = image::load_from_memory(image_slice).unwrap().to_rgba();
Some(self.create_image(image))
}
Resource::None => None,
}
}

View file

@ -9,7 +9,8 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>>
diag.current_path = testcase.absolute_path.clone();
let mut tr = typeregister::TypeRegister::builtin();
let doc = object_tree::Document::from_node(syntax_node, &mut diag, &mut tr);
run_passes(&doc, &mut diag, &mut tr);
let compiler_config = CompilerConfiguration::default();
run_passes(&doc, &mut diag, &mut tr, &compiler_config);
if diag.has_error() {
let vec = diag.inner.iter().map(|d| d.message.clone()).collect::<Vec<String>>();

View file

@ -15,7 +15,8 @@ fn main() -> std::io::Result<()> {
//println!("{:#?}", syntax_node);
let mut tr = typeregister::TypeRegister::builtin();
let doc = object_tree::Document::from_node(syntax_node, &mut diag, &mut tr);
run_passes(&doc, &mut diag, &mut tr);
let compiler_config = CompilerConfiguration::default();
run_passes(&doc, &mut diag, &mut tr, &compiler_config);
let (mut diag, source) = diag.check_and_exit_on_error(source);