mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-28 04:45:13 +00:00
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:
parent
82a7c3e070
commit
1404cb73ae
15 changed files with 105 additions and 98 deletions
|
@ -7,6 +7,10 @@ namespace sixtyfps {
|
||||||
|
|
||||||
union ResourceData {
|
union ResourceData {
|
||||||
SharedString absolute_file_path;
|
SharedString absolute_file_path;
|
||||||
|
struct {
|
||||||
|
void *ptr;
|
||||||
|
size_t len;
|
||||||
|
} slice;
|
||||||
|
|
||||||
ResourceData() { }
|
ResourceData() { }
|
||||||
~ResourceData() { }
|
~ResourceData() { }
|
||||||
|
|
|
@ -127,6 +127,7 @@ fn to_js_value<'cx>(val: interpreter::Value, cx: &mut impl Context<'cx>) -> Hand
|
||||||
Value::Resource(r) => match r {
|
Value::Resource(r) => match r {
|
||||||
Resource::None => JsUndefined::new().as_value(cx),
|
Resource::None => JsUndefined::new().as_value(cx),
|
||||||
Resource::AbsoluteFilePath(path) => JsString::new(cx, path.as_str()).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?
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,15 @@ pub fn compile(path: impl AsRef<std::path::Path>) -> Result<(), CompileError> {
|
||||||
|
|
||||||
let mut tr = typeregister::TypeRegister::builtin();
|
let mut tr = typeregister::TypeRegister::builtin();
|
||||||
let doc = object_tree::Document::from_node(syntax_node, &mut diag, &mut tr);
|
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() {
|
if diag.has_error() {
|
||||||
let vec = diag.inner.iter().map(|d| d.message.clone()).collect();
|
let vec = diag.inner.iter().map(|d| d.message.clone()).collect();
|
||||||
|
|
|
@ -139,7 +139,8 @@ pub fn sixtyfps(stream: TokenStream) -> TokenStream {
|
||||||
//println!("{:#?}", syntax_node);
|
//println!("{:#?}", syntax_node);
|
||||||
let mut tr = typeregister::TypeRegister::builtin();
|
let mut tr = typeregister::TypeRegister::builtin();
|
||||||
let tree = object_tree::Document::from_node(syntax_node, &mut diag, &mut tr);
|
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);
|
//println!("{:#?}", tree);
|
||||||
if diag.has_error() {
|
if diag.has_error() {
|
||||||
diag.map_offsets_to_span(&tokens);
|
diag.map_offsets_to_span(&tokens);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
sixtyfps_build::compile("src/hello.60").unwrap();
|
sixtyfps_build::compile("../cpptest/hello.60").unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: "-";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -84,7 +84,7 @@ pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option<TokenSt
|
||||||
quote!(#field_name.)
|
quote!(#field_name.)
|
||||||
};
|
};
|
||||||
let rust_property = quote!(#rust_property_accessor_prefix#rust_property_ident);
|
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) {
|
if matches!(item.lookup_property(k.as_str()), Type::Signal) {
|
||||||
init.push(quote!(
|
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 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!(
|
Some(quote!(
|
||||||
|
#(#resource_symbols)*
|
||||||
|
|
||||||
use sixtyfps::re_exports::const_field_offset;
|
use sixtyfps::re_exports::const_field_offset;
|
||||||
#[derive(sixtyfps::re_exports::FieldOffsets)]
|
#[derive(sixtyfps::re_exports::FieldOffsets)]
|
||||||
#[repr(C)]
|
#[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 {
|
match e {
|
||||||
Expression::StringLiteral(s) => quote!(sixtyfps::re_exports::SharedString::from(#s)),
|
Expression::StringLiteral(s) => quote!(sixtyfps::re_exports::SharedString::from(#s)),
|
||||||
Expression::NumberLiteral(n) => quote!(#n as _),
|
Expression::NumberLiteral(n) => quote!(#n as _),
|
||||||
Expression::Cast { from, to } => {
|
Expression::Cast { from, to } => {
|
||||||
let f = compile_expression(&*from);
|
let f = compile_expression(&*from, &component);
|
||||||
match (from.ty(), to) {
|
match (from.ty(), to) {
|
||||||
(Type::Float32, Type::String) | (Type::Int32, Type::String) => {
|
(Type::Float32, Type::String) | (Type::Int32, Type::String) => {
|
||||||
quote!(sixtyfps::re_exports::SharedString::from(format!("{}", #f).as_str()))
|
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))
|
quote!(_self.#access.get(context))
|
||||||
}
|
}
|
||||||
Expression::CodeBlock(sub) => {
|
Expression::CodeBlock(sub) => {
|
||||||
let map = sub.iter().map(|e| compile_expression(e));
|
let map = sub.iter().map(|e| compile_expression(e, &component));
|
||||||
quote!({ #(#map);* })
|
quote!({ #(#map);* })
|
||||||
}
|
}
|
||||||
Expression::SignalReference { element, name, .. } => {
|
Expression::SignalReference { element, name, .. } => {
|
||||||
|
@ -196,7 +208,7 @@ fn compile_expression(e: &Expression) -> TokenStream {
|
||||||
}
|
}
|
||||||
Expression::FunctionCall { function } => {
|
Expression::FunctionCall { function } => {
|
||||||
if matches!(function.ty(), Type::Signal) {
|
if matches!(function.ty(), Type::Signal) {
|
||||||
let base = compile_expression(function);
|
let base = compile_expression(function, &component);
|
||||||
quote!(#base.emit(&context, ()))
|
quote!(#base.emit(&context, ()))
|
||||||
} else {
|
} else {
|
||||||
let error = format!("the function {:?} is not a signal", e);
|
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::SelfAssignement { lhs, rhs, op } => match &**lhs {
|
||||||
Expression::PropertyReference { element, name, .. } => {
|
Expression::PropertyReference { element, name, .. } => {
|
||||||
let lhs = access_member(&element.upgrade().unwrap(), name.as_str());
|
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);
|
let op = proc_macro2::Punct::new(*op, proc_macro2::Spacing::Alone);
|
||||||
quote!( _self.#lhs.set(_self.#lhs.get(context) #op &(#rhs) ))
|
quote!( _self.#lhs.set(_self.#lhs.get(context) #op &(#rhs) ))
|
||||||
}
|
}
|
||||||
_ => panic!("typechecking should make sure this was a PropertyReference"),
|
_ => panic!("typechecking should make sure this was a PropertyReference"),
|
||||||
},
|
},
|
||||||
Expression::ResourceReference { absolute_source_path } => {
|
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)))
|
quote!(sixtyfps::re_exports::Resource::AbsoluteFilePath(sixtyfps::re_exports::SharedString::from(#absolute_source_path)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let error = format!("unsupported expression {:?}", e);
|
let error = format!("unsupported expression {:?}", e);
|
||||||
quote!(compile_error! {#error})
|
quote!(compile_error! {#error})
|
||||||
|
|
|
@ -19,6 +19,7 @@ pub mod parser;
|
||||||
pub mod typeregister;
|
pub mod typeregister;
|
||||||
|
|
||||||
mod passes {
|
mod passes {
|
||||||
|
pub mod collect_resources;
|
||||||
pub mod inlining;
|
pub mod inlining;
|
||||||
pub mod lower_layout;
|
pub mod lower_layout;
|
||||||
pub mod move_declarations;
|
pub mod move_declarations;
|
||||||
|
@ -26,14 +27,23 @@ mod passes {
|
||||||
pub mod unique_id;
|
pub mod unique_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CompilerConfiguration {
|
||||||
|
pub embed_resources: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_passes(
|
pub fn run_passes(
|
||||||
doc: &object_tree::Document,
|
doc: &object_tree::Document,
|
||||||
diag: &mut diagnostics::Diagnostics,
|
diag: &mut diagnostics::Diagnostics,
|
||||||
tr: &mut typeregister::TypeRegister,
|
tr: &mut typeregister::TypeRegister,
|
||||||
|
compiler_config: &CompilerConfiguration,
|
||||||
) {
|
) {
|
||||||
passes::resolving::resolve_expressions(doc, diag, tr);
|
passes::resolving::resolve_expressions(doc, diag, tr);
|
||||||
passes::inlining::inline(doc);
|
passes::inlining::inline(doc);
|
||||||
passes::lower_layout::lower_layouts(&doc.root_component, diag);
|
passes::lower_layout::lower_layouts(&doc.root_component, diag);
|
||||||
passes::unique_id::assign_unique_id(&doc.root_component);
|
passes::unique_id::assign_unique_id(&doc.root_component);
|
||||||
passes::move_declarations::move_declarations(&doc.root_component);
|
passes::move_declarations::move_declarations(&doc.root_component);
|
||||||
|
if compiler_config.embed_resources {
|
||||||
|
passes::collect_resources::collect_resources(&doc.root_component);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,11 @@ pub struct Component {
|
||||||
/// List of elements that are not attached to the root anymore because they have been
|
/// 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
|
/// optimized away, but their properties may still be in use
|
||||||
pub optimized_elements: RefCell<Vec<Rc<RefCell<Element>>>>,
|
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 {
|
impl Component {
|
||||||
|
|
30
sixtyfps_compiler/passes/collect_resources.rs
Normal file
30
sixtyfps_compiler/passes/collect_resources.rs
Normal 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)
|
||||||
|
}
|
|
@ -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
|
/// 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.
|
/// is necessary before they can be used.
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
#[repr(C)]
|
#[repr(u8)]
|
||||||
pub enum Resource {
|
pub enum Resource {
|
||||||
/// A resource that does not represent any data.
|
/// A resource that does not represent any data.
|
||||||
None,
|
None,
|
||||||
/// A resource that points to a file in the file system
|
/// A resource that points to a file in the file system
|
||||||
AbsoluteFilePath(crate::SharedString),
|
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 {
|
impl Default for Resource {
|
||||||
|
|
|
@ -92,7 +92,8 @@ pub fn load(
|
||||||
if !diag.inner.is_empty() {
|
if !diag.inner.is_empty() {
|
||||||
return Err(diag);
|
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() {
|
if !diag.inner.is_empty() {
|
||||||
return Err(diag);
|
return Err(diag);
|
||||||
}
|
}
|
||||||
|
|
|
@ -314,6 +314,11 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder {
|
||||||
let image = image::open(image_path.as_path()).unwrap().into_rgba();
|
let image = image::open(image_path.as_path()).unwrap().into_rgba();
|
||||||
Some(self.create_image(image))
|
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,
|
Resource::None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>>
|
||||||
diag.current_path = testcase.absolute_path.clone();
|
diag.current_path = testcase.absolute_path.clone();
|
||||||
let mut tr = typeregister::TypeRegister::builtin();
|
let mut tr = typeregister::TypeRegister::builtin();
|
||||||
let doc = object_tree::Document::from_node(syntax_node, &mut diag, &mut tr);
|
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() {
|
if diag.has_error() {
|
||||||
let vec = diag.inner.iter().map(|d| d.message.clone()).collect::<Vec<String>>();
|
let vec = diag.inner.iter().map(|d| d.message.clone()).collect::<Vec<String>>();
|
||||||
|
|
|
@ -15,7 +15,8 @@ fn main() -> std::io::Result<()> {
|
||||||
//println!("{:#?}", syntax_node);
|
//println!("{:#?}", syntax_node);
|
||||||
let mut tr = typeregister::TypeRegister::builtin();
|
let mut tr = typeregister::TypeRegister::builtin();
|
||||||
let doc = object_tree::Document::from_node(syntax_node, &mut diag, &mut tr);
|
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);
|
let (mut diag, source) = diag.check_and_exit_on_error(source);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue