From a01bdd66c5ff6cdeb1386f8c20e3ba3c565a7d5c Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Mon, 18 May 2020 12:49:27 -0400 Subject: [PATCH 01/37] isZero, isPositive, isNegative --- compiler/can/src/builtins.rs | 51 +++++++++ compiler/gen/tests/gen_builtins.rs | 146 ++++++++++++++++-------- compiler/gen/tests/gen_primitives.rs | 50 ++++---- compiler/gen/tests/gen_tags.rs | 18 +-- compiler/module/src/symbol.rs | 6 + compiler/solve/tests/test_uniq_solve.rs | 14 ++- 6 files changed, 198 insertions(+), 87 deletions(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index c4fcf9544f..99a369171f 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -34,9 +34,60 @@ pub fn builtin_defs(var_store: &VarStore) -> Vec { int_rem(var_store), int_is_odd(var_store), int_is_even(var_store), + int_is_zero(var_store), + int_is_positive(var_store), + int_is_negative(var_store), ] } +/// Int.isNegative : Int -> Bool +fn int_is_negative(var_store: &VarStore) -> Def { + use crate::expr::Expr::*; + + defn( + Symbol::INT_IS_NEGATIVE, + vec![Symbol::INT_IS_NEGATIVE_ARG], + var_store, + call( + Symbol::NUM_LT, + vec![Var(Symbol::INT_IS_NEGATIVE_ARG), Int(var_store.fresh(), 0)], + var_store, + ), + ) +} + +/// Int.isPositive : Int -> Bool +fn int_is_positive(var_store: &VarStore) -> Def { + use crate::expr::Expr::*; + + defn( + Symbol::INT_IS_POSITIVE, + vec![Symbol::INT_IS_POSITIVE_ARG], + var_store, + call( + Symbol::NUM_GT, + vec![Var(Symbol::INT_IS_POSITIVE_ARG), Int(var_store.fresh(), 0)], + var_store, + ), + ) +} + +/// Int.isZero : Int -> Bool +fn int_is_zero(var_store: &VarStore) -> Def { + use crate::expr::Expr::*; + + defn( + Symbol::INT_IS_ZERO, + vec![Symbol::INT_IS_ZERO_ARG], + var_store, + call( + Symbol::INT_EQ_I64, + vec![Var(Symbol::INT_IS_ZERO_ARG), Int(var_store.fresh(), 0)], + var_store, + ), + ) +} + /// Int.isOdd : Int -> Bool fn int_is_odd(var_store: &VarStore) -> Def { use crate::expr::Expr::*; diff --git a/compiler/gen/tests/gen_builtins.rs b/compiler/gen/tests/gen_builtins.rs index dd9b928612..c3b9f849cc 100644 --- a/compiler/gen/tests/gen_builtins.rs +++ b/compiler/gen/tests/gen_builtins.rs @@ -29,18 +29,24 @@ mod gen_builtins { #[test] fn f64_sqrt() { - assert_evals_to!("Float.sqrt 144", 12.0, f64); + with_larger_debug_stack(|| { + assert_evals_to!("Float.sqrt 144", 12.0, f64); + }) } #[test] fn f64_round() { - assert_evals_to!("Float.round 3.6", 4, i64); + with_larger_debug_stack(|| { + assert_evals_to!("Float.round 3.6", 4, i64); + }) } #[test] fn f64_abs() { - assert_evals_to!("Float.abs -4.7", 4.7, f64); - assert_evals_to!("Float.abs 5.8", 5.8, f64); + with_larger_debug_stack(|| { + assert_evals_to!("Float.abs -4.7", 4.7, f64); + assert_evals_to!("Float.abs 5.8", 5.8, f64); + }) } #[test] @@ -51,7 +57,9 @@ mod gen_builtins { #[test] fn empty_list_literal() { - assert_evals_to!("[]", &[], &'static [i64]); + with_larger_debug_stack(|| { + assert_evals_to!("[]", &[], &'static [i64]); + }) } #[test] @@ -61,9 +69,10 @@ mod gen_builtins { #[test] fn gen_if_fn() { - assert_evals_to!( - indoc!( - r#" + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" limitedNegate = \num -> if num == 1 then -1 @@ -74,10 +83,11 @@ mod gen_builtins { limitedNegate 1 "# - ), - -1, - i64 - ); + ), + -1, + i64 + ); + }) } #[test] @@ -95,15 +105,17 @@ mod gen_builtins { #[test] fn gen_add_f64() { - assert_evals_to!( - indoc!( - r#" + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" 1.1 + 2.4 + 3 "# - ), - 6.5, - f64 - ); + ), + 6.5, + f64 + ); + }) } #[test] @@ -147,15 +159,17 @@ mod gen_builtins { #[test] fn gen_add_i64() { - assert_evals_to!( - indoc!( - r#" + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" 1 + 2 + 3 "# - ), - 6, - i64 - ); + ), + 6, + i64 + ); + }) } #[test] @@ -257,6 +271,26 @@ mod gen_builtins { ); } + #[test] + fn gen_is_zero() { + assert_evals_to!("Int.isZero 0", true, bool); + assert_evals_to!("Int.isZero 1", false, bool); + } + + #[test] + fn gen_is_positive() { + assert_evals_to!("Int.isPositive 0", false, bool); + assert_evals_to!("Int.isPositive 1", true, bool); + assert_evals_to!("Int.isPositive -5", false, bool); + } + + #[test] + fn gen_is_negative() { + assert_evals_to!("Int.isNegative 0", false, bool); + assert_evals_to!("Int.isNegative 3", false, bool); + assert_evals_to!("Int.isNegative -2", false, bool); + } + #[test] fn gen_is_odd() { assert_evals_to!("Int.isOdd 4", false, bool); @@ -387,9 +421,10 @@ mod gen_builtins { } #[test] fn tail_call_elimination() { - assert_evals_to!( - indoc!( - r#" + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" sum = \n, accum -> when n is 0 -> accum @@ -397,10 +432,11 @@ mod gen_builtins { sum 1_000_000 0 "# - ), - 500000500000, - i64 - ); + ), + 500000500000, + i64 + ); + }) } #[test] fn int_negate() { @@ -425,12 +461,16 @@ mod gen_builtins { #[test] fn empty_list_len() { - assert_evals_to!("List.len []", 0, usize); + with_larger_debug_stack(|| { + assert_evals_to!("List.len []", 0, usize); + }) } #[test] fn basic_int_list_len() { - assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize); + with_larger_debug_stack(|| { + assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize); + }) } #[test] @@ -472,37 +512,43 @@ mod gen_builtins { #[test] fn empty_list_is_empty() { - assert_evals_to!("List.isEmpty []", true, bool); + with_larger_debug_stack(|| { + assert_evals_to!("List.isEmpty []", true, bool); + }) } #[test] fn first_int_list() { - assert_evals_to!( - indoc!( - r#" + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" when List.first [ 12, 9, 6, 3 ] is Ok val -> val Err _ -> -1 "# - ), - 12, - i64 - ); + ), + 12, + i64 + ); + }) } #[test] fn first_empty_list() { - assert_evals_to!( - indoc!( - r#" + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" when List.first [] is Ok val -> val Err _ -> -1 "# - ), - -1, - i64 - ); + ), + -1, + i64 + ); + }) } #[test] diff --git a/compiler/gen/tests/gen_primitives.rs b/compiler/gen/tests/gen_primitives.rs index 0818c0e144..8923cb24f8 100644 --- a/compiler/gen/tests/gen_primitives.rs +++ b/compiler/gen/tests/gen_primitives.rs @@ -13,7 +13,7 @@ mod helpers; #[cfg(test)] mod gen_primitives { - use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; + use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut}; use bumpalo::Bump; use inkwell::context::Context; use inkwell::execution_engine::JitFunction; @@ -406,27 +406,30 @@ mod gen_primitives { #[test] fn gen_chained_defs() { - assert_evals_to!( - indoc!( - r#" - x = i1 - i3 = i2 - i1 = 1337 - i2 = i1 - y = 12.4 - - i3 - "# - ), - 1337, - i64 - ); + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" + x = i1 + i3 = i2 + i1 = 1337 + i2 = i1 + y = 12.4 + + i3 + "# + ), + 1337, + i64 + ); + }) } #[test] fn gen_nested_defs() { - assert_evals_to!( - indoc!( - r#" + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" x = 5 answer = @@ -457,9 +460,10 @@ mod gen_primitives { answer "# - ), - 1337, - i64 - ); + ), + 1337, + i64 + ); + }) } } diff --git a/compiler/gen/tests/gen_tags.rs b/compiler/gen/tests/gen_tags.rs index 96ae1a3477..1e04a956ca 100644 --- a/compiler/gen/tests/gen_tags.rs +++ b/compiler/gen/tests/gen_tags.rs @@ -13,7 +13,7 @@ mod helpers; #[cfg(test)] mod gen_tags { - use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; + use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut}; use bumpalo::Bump; use inkwell::context::Context; use inkwell::execution_engine::JitFunction; @@ -213,9 +213,10 @@ mod gen_tags { #[test] fn even_odd() { - assert_evals_to!( - indoc!( - r#" + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" even = \n -> when n is 0 -> True @@ -230,10 +231,11 @@ mod gen_tags { odd 5 && even 42 "# - ), - true, - bool - ); + ), + true, + bool + ); + }) } #[test] diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 735a43de61..3f097b6d5b 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -623,6 +623,12 @@ define_builtins! { 29 INT_IS_ODD_ARG: "isOdd#arg" 30 INT_IS_EVEN: "isEven" 31 INT_IS_EVEN_ARG: "isEven#arg" + 32 INT_IS_ZERO: "isZero" + 33 INT_IS_ZERO_ARG: "isZero#arg" + 34 INT_IS_POSITIVE: "isPositive" + 35 INT_IS_POSITIVE_ARG: "isPositive#arg" + 36 INT_IS_NEGATIVE: "isNegative" + 37 INT_IS_NEGATIVE_ARG: "isNegative#arg" } 3 FLOAT: "Float" => { 0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias diff --git a/compiler/solve/tests/test_uniq_solve.rs b/compiler/solve/tests/test_uniq_solve.rs index 717c518b4d..4a81e568fb 100644 --- a/compiler/solve/tests/test_uniq_solve.rs +++ b/compiler/solve/tests/test_uniq_solve.rs @@ -116,14 +116,16 @@ mod test_uniq_solve { #[test] fn empty_list_literal() { - infer_eq( - indoc!( - r#" + with_larger_debug_stack(|| { + infer_eq( + indoc!( + r#" [] "# - ), - "Attr * (List *)", - ); + ), + "Attr * (List *)", + ); + }) } #[test] From c83a6dc7b83a04ea519db3be74f0eb3a78813ddc Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Fri, 22 May 2020 23:05:17 -0400 Subject: [PATCH 02/37] Float function isPositive, isNegative, and isZero --- compiler/builtins/src/std.rs | 12 ++++++ compiler/builtins/src/unique.rs | 12 ++++++ compiler/can/src/builtins.rs | 60 ++++++++++++++++++++++++++++ compiler/gen/tests/gen_builtins.rs | 64 ++++++++++++++++++------------ compiler/module/src/symbol.rs | 10 ++++- 5 files changed, 130 insertions(+), 28 deletions(-) diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index d7503fc605..985b95f0bc 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -331,6 +331,18 @@ pub fn types() -> MutMap { // Float module + // isGt or (>) : Num a, Num a -> Bool + add_type( + Symbol::FLOAT_GT, + SolvedType::Func(vec![float_type(), float_type()], Box::new(bool_type())), + ); + + // eq or (==) : Num a, Num a -> Bool + add_type( + Symbol::FLOAT_EQ, + SolvedType::Func(vec![float_type(), float_type()], Box::new(bool_type())), + ); + // div : Float, Float -> Float add_type( Symbol::FLOAT_DIV, diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 5521db13d5..70c4198a5f 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -415,6 +415,18 @@ pub fn types() -> MutMap { // Float module + // isGt or (>) : Num a, Num a -> Bool + add_type( + Symbol::FLOAT_GT, + unique_function(vec![float_type(UVAR1), float_type(UVAR2)], bool_type(UVAR3)), + ); + + // eq or (==) : Num a, Num a -> Bool + add_type( + Symbol::FLOAT_EQ, + unique_function(vec![float_type(UVAR1), float_type(UVAR2)], bool_type(UVAR3)), + ); + // div : Float, Float -> Float add_type( Symbol::FLOAT_DIV, diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 99a369171f..55feccc707 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -37,9 +37,69 @@ pub fn builtin_defs(var_store: &VarStore) -> Vec { int_is_zero(var_store), int_is_positive(var_store), int_is_negative(var_store), + float_is_positive(var_store), + float_is_negative(var_store), + float_is_zero(var_store), ] } +/// Float.isZero : Float -> Bool +fn float_is_zero(var_store: &VarStore) -> Def { + use crate::expr::Expr::*; + + defn( + Symbol::FLOAT_IS_ZERO, + vec![Symbol::FLOAT_IS_ZERO_ARG], + var_store, + call( + Symbol::FLOAT_EQ, + vec![ + Float(var_store.fresh(), 0.0), + Var(Symbol::FLOAT_IS_ZERO_ARG), + ], + var_store, + ), + ) +} + +/// Float.isNegative : Float -> Bool +fn float_is_negative(var_store: &VarStore) -> Def { + use crate::expr::Expr::*; + + defn( + Symbol::FLOAT_IS_NEGATIVE, + vec![Symbol::FLOAT_IS_NEGATIVE_ARG], + var_store, + call( + Symbol::FLOAT_GT, + vec![ + Float(var_store.fresh(), 0.0), + Var(Symbol::FLOAT_IS_NEGATIVE_ARG), + ], + var_store, + ), + ) +} + +/// Float.isPositive : Float -> Bool +fn float_is_positive(var_store: &VarStore) -> Def { + use crate::expr::Expr::*; + + defn( + Symbol::FLOAT_IS_POSITIVE, + vec![Symbol::FLOAT_IS_POSITIVE_ARG], + var_store, + call( + Symbol::FLOAT_GT, + vec![ + Var(Symbol::FLOAT_IS_POSITIVE_ARG), + Float(var_store.fresh(), 0.0), + ], + var_store, + ), + ) +} + /// Int.isNegative : Int -> Bool fn int_is_negative(var_store: &VarStore) -> Def { use crate::expr::Expr::*; diff --git a/compiler/gen/tests/gen_builtins.rs b/compiler/gen/tests/gen_builtins.rs index c3b9f849cc..015645326b 100644 --- a/compiler/gen/tests/gen_builtins.rs +++ b/compiler/gen/tests/gen_builtins.rs @@ -29,24 +29,18 @@ mod gen_builtins { #[test] fn f64_sqrt() { - with_larger_debug_stack(|| { - assert_evals_to!("Float.sqrt 144", 12.0, f64); - }) + assert_evals_to!("Float.sqrt 144", 12.0, f64); } #[test] fn f64_round() { - with_larger_debug_stack(|| { - assert_evals_to!("Float.round 3.6", 4, i64); - }) + assert_evals_to!("Float.round 3.6", 4, i64); } #[test] fn f64_abs() { - with_larger_debug_stack(|| { - assert_evals_to!("Float.abs -4.7", 4.7, f64); - assert_evals_to!("Float.abs 5.8", 5.8, f64); - }) + assert_evals_to!("Float.abs -4.7", 4.7, f64); + assert_evals_to!("Float.abs 5.8", 5.8, f64); } #[test] @@ -57,9 +51,7 @@ mod gen_builtins { #[test] fn empty_list_literal() { - with_larger_debug_stack(|| { - assert_evals_to!("[]", &[], &'static [i64]); - }) + assert_evals_to!("[]", &[], &'static [i64]); } #[test] @@ -69,10 +61,9 @@ mod gen_builtins { #[test] fn gen_if_fn() { - with_larger_debug_stack(|| { - assert_evals_to!( - indoc!( - r#" + assert_evals_to!( + indoc!( + r#" limitedNegate = \num -> if num == 1 then -1 @@ -83,11 +74,10 @@ mod gen_builtins { limitedNegate 1 "# - ), - -1, - i64 - ); - }) + ), + -1, + i64 + ); } #[test] @@ -272,23 +262,45 @@ mod gen_builtins { } #[test] - fn gen_is_zero() { + fn gen_is_zero_i64() { assert_evals_to!("Int.isZero 0", true, bool); assert_evals_to!("Int.isZero 1", false, bool); } #[test] - fn gen_is_positive() { + fn gen_is_positive_i64() { assert_evals_to!("Int.isPositive 0", false, bool); assert_evals_to!("Int.isPositive 1", true, bool); assert_evals_to!("Int.isPositive -5", false, bool); } #[test] - fn gen_is_negative() { + fn gen_is_negative_i64() { assert_evals_to!("Int.isNegative 0", false, bool); assert_evals_to!("Int.isNegative 3", false, bool); - assert_evals_to!("Int.isNegative -2", false, bool); + assert_evals_to!("Int.isNegative -2", true, bool); + } + + #[test] + fn gen_is_positive_f64() { + assert_evals_to!("Float.isPositive 0.0", false, bool); + assert_evals_to!("Float.isPositive 4.7", true, bool); + assert_evals_to!("Float.isPositive -8.5", false, bool); + } + + #[test] + fn gen_is_negative_f64() { + assert_evals_to!("Float.isNegative 0.0", false, bool); + assert_evals_to!("Float.isNegative 9.9", false, bool); + assert_evals_to!("Float.isNegative -4.4", true, bool); + } + + #[test] + fn gen_is_zero_f64() { + assert_evals_to!("Float.isZero 0", true, bool); + assert_evals_to!("Float.isZero 0_0", true, bool); + assert_evals_to!("Float.isZero 0.0", true, bool); + assert_evals_to!("Float.isZero 1", false, bool); } #[test] diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 3f097b6d5b..852c6ddd37 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -641,13 +641,19 @@ define_builtins! { 7 FLOAT_LOWEST: "lowest" 8 FLOAT_ADD: "#add" 9 FLOAT_SUB: "#sub" - 10 FLOAT_EQ: "#eq" + 10 FLOAT_EQ: "eq" 11 FLOAT_ROUND: "round" 12 FLOAT_LT: "#lt" 13 FLOAT_LTE: "#lte" - 14 FLOAT_GT: "#gt" + 14 FLOAT_GT: "gt" 15 FLOAT_GTE: "#gte" 16 FLOAT_ABS: "abs" + 17 FLOAT_IS_POSITIVE: "isPositive" + 18 FLOAT_IS_POSITIVE_ARG: "isPositive#arg" + 19 FLOAT_IS_NEGATIVE: "isNegative" + 20 FLOAT_IS_NEGATIVE_ARG: "isNegative#arg" + 21 FLOAT_IS_ZERO: "isZero" + 22 FLOAT_IS_ZERO_ARG: "isZero#arg" } 4 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias From 5c1c9efb896d9679ccb964cbe909f5ab29ff8c9e Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Mon, 25 May 2020 16:28:41 -0400 Subject: [PATCH 03/37] Added trigonometric functions to code gen and builtins --- compiler/builtins/src/std.rs | 18 ++++++++++++++++++ compiler/builtins/src/unique.rs | 18 ++++++++++++++++++ compiler/can/src/builtins.rs | 28 ++++++++++++++++++++++++++++ compiler/gen/src/llvm/build.rs | 16 ++++++++++++++++ compiler/gen/tests/gen_builtins.rs | 18 ++++++++++++++++++ compiler/module/src/symbol.rs | 4 ++++ 6 files changed, 102 insertions(+) diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 985b95f0bc..936ebe9ea5 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -373,6 +373,24 @@ pub fn types() -> MutMap { SolvedType::Func(vec![float_type()], Box::new(float_type())), ); + // sin : Float -> Float + add_type( + Symbol::FLOAT_SIN, + SolvedType::Func(vec![float_type()], Box::new(float_type())), + ); + + // cos : Float -> Float + add_type( + Symbol::FLOAT_COS, + SolvedType::Func(vec![float_type()], Box::new(float_type())), + ); + + // tan : Float -> Float + add_type( + Symbol::FLOAT_TAN, + SolvedType::Func(vec![float_type()], Box::new(float_type())), + ); + // highest : Float add_type(Symbol::FLOAT_HIGHEST, float_type()); diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 70c4198a5f..e75fe3398a 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -463,6 +463,24 @@ pub fn types() -> MutMap { unique_function(vec![float_type(UVAR1)], float_type(UVAR2)), ); + // sin : Float -> Float + add_type( + Symbol::FLOAT_SIN, + unique_function(vec![float_type(UVAR1)], float_type(UVAR2)), + ); + + // cos : Float -> Float + add_type( + Symbol::FLOAT_COS, + unique_function(vec![float_type(UVAR1)], float_type(UVAR2)), + ); + + // tan : Float -> Float + add_type( + Symbol::FLOAT_TAN, + unique_function(vec![float_type(UVAR1)], float_type(UVAR2)), + ); + // highest : Float add_type(Symbol::FLOAT_HIGHEST, float_type(UVAR1)); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 55feccc707..d7322ea617 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -40,9 +40,37 @@ pub fn builtin_defs(var_store: &VarStore) -> Vec { float_is_positive(var_store), float_is_negative(var_store), float_is_zero(var_store), + float_tan(var_store), ] } +/// Float.tan : Float -> Float +fn float_tan(var_store: &VarStore) -> Def { + use crate::expr::Expr::*; + + defn( + Symbol::FLOAT_TAN, + vec![Symbol::FLOAT_TAN_ARG], + var_store, + call( + Symbol::FLOAT_DIV, + vec![ + call( + Symbol::FLOAT_SIN, + vec![Var(Symbol::FLOAT_TAN_ARG)], + var_store, + ), + call( + Symbol::FLOAT_COS, + vec![Var(Symbol::FLOAT_TAN_ARG)], + var_store, + ), + ], + var_store, + ), + ) +} + /// Float.isZero : Float -> Bool fn float_is_zero(var_store: &VarStore) -> Def { use crate::expr::Expr::*; diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 2d1e5bbec3..aca03ada10 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -87,11 +87,25 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { LLVM_FABS_F64, f64_type.fn_type(&[f64_type.into()], false), ); + + add_intrinsic( + module, + LLVM_SIN_F64, + f64_type.fn_type(&[f64_type.into()], false), + ); + + add_intrinsic( + module, + LLVM_COS_F64, + f64_type.fn_type(&[f64_type.into()], false), + ); } static LLVM_SQRT_F64: &str = "llvm.sqrt.f64"; static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64"; static LLVM_FABS_F64: &str = "llvm.fabs.f64"; +static LLVM_SIN_F64: &str = "llvm.sin.f64"; +static LLVM_COS_F64: &str = "llvm.cos.f64"; fn add_intrinsic<'ctx>( module: &Module<'ctx>, @@ -1193,6 +1207,8 @@ fn call_with_args<'a, 'ctx, 'env>( BasicValueEnum::IntValue(bool_val) } + Symbol::FLOAT_SIN => call_intrinsic(LLVM_SIN_F64, env, args), + Symbol::FLOAT_COS => call_intrinsic(LLVM_COS_F64, env, args), Symbol::NUM_MUL => { debug_assert!(args.len() == 2); diff --git a/compiler/gen/tests/gen_builtins.rs b/compiler/gen/tests/gen_builtins.rs index 015645326b..5098d62a35 100644 --- a/compiler/gen/tests/gen_builtins.rs +++ b/compiler/gen/tests/gen_builtins.rs @@ -315,6 +315,24 @@ mod gen_builtins { assert_evals_to!("Int.isEven 7", false, bool); } + #[test] + fn sin() { + assert_evals_to!("Float.sin 0", 0.0, f64); + assert_evals_to!("Float.sin 1.41421356237", 0.9877659459922529, f64); + } + + #[test] + fn cos() { + assert_evals_to!("Float.cos 0", 1.0, f64); + assert_evals_to!("Float.cos 3.14159265359", -1.0, f64); + } + + #[test] + fn tan() { + assert_evals_to!("Float.tan 0", 0.0, f64); + assert_evals_to!("Float.tan 1", 1.557407724654902, f64); + } + #[test] fn lt_i64() { assert_evals_to!("1 < 2", true, bool); diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 852c6ddd37..02709876f8 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -654,6 +654,10 @@ define_builtins! { 20 FLOAT_IS_NEGATIVE_ARG: "isNegative#arg" 21 FLOAT_IS_ZERO: "isZero" 22 FLOAT_IS_ZERO_ARG: "isZero#arg" + 23 FLOAT_SIN: "sin" + 24 FLOAT_COS: "cos" + 25 FLOAT_TAN: "tan" + 26 FLOAT_TAN_ARG: "tan#arg" } 4 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias From deda27304e206ebb0deadca1765afba838387d51 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 27 May 2020 21:27:18 -0400 Subject: [PATCH 04/37] Use PresentMode::IMMEDIATE --- editor/src/lib.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 97edea7596..cff7fe34e6 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -1,6 +1,6 @@ use gfx_hal::{ device::Device, - window::{Extent2D, PresentationSurface, Surface}, + window::{Extent2D, PresentMode, PresentationSurface, Surface}, Instance, }; use glsl_to_spirv::ShaderType; @@ -80,13 +80,7 @@ fn run_event_loop() { // TODO do a better window size const WINDOW_SIZE: [u32; 2] = [512, 512]; - // TODO try configuring the swapchain explicitly, in particular in order - // to experiment with different PresentMode settings to see how they - // affect input latency. - // - // https://rust-tutorials.github.io/learn-gfx-hal/03_clear_the_window.html let event_loop = EventLoop::new(); - let (logical_window_size, physical_window_size) = { use winit::dpi::{LogicalSize, PhysicalSize}; @@ -413,7 +407,8 @@ fn run_event_loop() { let caps = res.surface.capabilities(&adapter.physical_device); let mut swapchain_config = - SwapchainConfig::from_caps(&caps, surface_color_format, surface_extent); + SwapchainConfig::from_caps(&caps, surface_color_format, surface_extent) + .with_present_mode(PresentMode::IMMEDIATE); // This seems to fix some fullscreen slowdown on macOS. if caps.image_count.contains(&3) { From a87667c74fadfd1b2ecdbd90b34de6231f2f6834 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 27 May 2020 21:23:13 -0400 Subject: [PATCH 05/37] Add text shaders (segfaults) --- Cargo.lock | 108 +++++++++++++++++++++++++++++++++++++++ editor/Cargo.toml | 1 + editor/shaders/text.frag | 17 ++++++ editor/shaders/text.vert | 53 +++++++++++++++++++ editor/src/lib.rs | 90 +++++++++++++++++++++++--------- 5 files changed, 246 insertions(+), 23 deletions(-) create mode 100644 editor/shaders/text.frag create mode 100644 editor/shaders/text.vert diff --git a/Cargo.lock b/Cargo.lock index 8605000b24..2dbbf6fd5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "ab_glyph" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89080d12ceb15b6e4c748e1e0ec048bf97ef28fa20c285dd411738a9d648b08a" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7e4e8cf778db814365e46839949ca74df4efb10e87ba4913e6ec5967ef0285" + [[package]] name = "adler32" version = "1.0.4" @@ -431,6 +447,16 @@ dependencies = [ "itertools", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" +dependencies = [ + "crossbeam-utils", + "maybe-uninit", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -764,6 +790,45 @@ dependencies = [ "tempfile", ] +[[package]] +name = "glyph_brush" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afd3e2cfd503a5218dd56172a8bf7c8655a4a7cf745737c606a6edfeea1b343f" +dependencies = [ + "glyph_brush_draw_cache", + "glyph_brush_layout", + "log", + "ordered-float", + "rustc-hash", + "twox-hash", +] + +[[package]] +name = "glyph_brush_draw_cache" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588662439c80d2784e6bb1274e2a6378ffcc74f8006b02e67e905039e200764d" +dependencies = [ + "ab_glyph", + "crossbeam-channel", + "crossbeam-deque", + "linked-hash-map", + "rayon", + "rustc-hash", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa49abf7dcf7bfe68f42c1c8ab7473505aaba14de84afb8899a0109b6c61717" +dependencies = [ + "ab_glyph", + "approx", + "xi-unicode", +] + [[package]] name = "heck" version = "0.3.1" @@ -995,6 +1060,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + [[package]] name = "llvm-sys" version = "100.0.1" @@ -1293,6 +1364,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "owned_ttf_parser" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" +dependencies = [ + "ttf-parser", +] + [[package]] name = "parking_lot" version = "0.10.2" @@ -1830,6 +1910,7 @@ dependencies = [ "gfx-backend-vulkan", "gfx-hal", "glsl-to-spirv", + "glyph_brush", "im", "im-rc", "image", @@ -2120,6 +2201,12 @@ dependencies = [ "roc_types", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2450,6 +2537,21 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "ttf-parser" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52fbe7769f5af5d7d25aea74b9443b64e544a5ffb4d2b2968295ddea934f1a06" + +[[package]] +name = "twox-hash" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56" +dependencies = [ + "rand 0.7.3", +] + [[package]] name = "typed-arena" version = "2.0.1" @@ -2796,6 +2898,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" +[[package]] +name = "xi-unicode" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7395cdb9d0a6219fa0ea77d08c946adf9c1984c72fcd443ace30365f3daadef7" + [[package]] name = "xml-rs" version = "0.8.3" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index fdaea372ed..5a331ea6b3 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -50,6 +50,7 @@ inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.releas target-lexicon = "0.10" winit = "0.22" image = "0.23" +glyph_brush="0.7" gfx-hal = "0.5" glsl-to-spirv = "0.1" bincode = "1.2" diff --git a/editor/shaders/text.frag b/editor/shaders/text.frag new file mode 100644 index 0000000000..5492b796fa --- /dev/null +++ b/editor/shaders/text.frag @@ -0,0 +1,17 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +uniform sampler2D font_tex; + +layout(location = 0) in vec2 f_tex_pos; +layout(location = 1) in vec4 f_color; + +layout(location = 0) out vec4 out_color; + +void main() { + float alpha = texture(font_tex, f_tex_pos).r; + if (alpha <= 0.0) { + discard; + } + out_color = f_color * vec4(1.0, 1.0, 1.0, alpha); +} diff --git a/editor/shaders/text.vert b/editor/shaders/text.vert new file mode 100644 index 0000000000..8aaf383746 --- /dev/null +++ b/editor/shaders/text.vert @@ -0,0 +1,53 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +const mat4 INVERT_Y_AXIS = mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, -1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) +); + +/* layout(push_constant) uniform Transform { */ +/* mat4 matrix; */ +/* } transform; */ + +layout(location = 0) in vec3 left_top; +layout(location = 1) in vec2 right_bottom; +layout(location = 2) in vec2 tex_left_top; +layout(location = 3) in vec2 tex_right_bottom; +layout(location = 4) in vec4 color; + +layout(location = 0) out vec2 f_tex_pos; +layout(location = 1) out vec4 f_color; + +// generate positional data based on vertex ID +void main() { + vec2 pos = vec2(0.0); + float left = left_top.x; + float right = right_bottom.x; + float top = left_top.y; + float bottom = right_bottom.y; + + switch (gl_VertexIndex) { + case 0: + pos = vec2(left, top); + f_tex_pos = tex_left_top; + break; + case 1: + pos = vec2(right, top); + f_tex_pos = vec2(tex_right_bottom.x, tex_left_top.y); + break; + case 2: + pos = vec2(left, bottom); + f_tex_pos = vec2(tex_left_top.x, tex_right_bottom.y); + break; + case 3: + pos = vec2(right, bottom); + f_tex_pos = tex_right_bottom; + break; + } + + f_color = color; + gl_Position = INVERT_Y_AXIS * /*transform.matrix * */ vec4(pos, left_top.z, 1.0); +} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index cff7fe34e6..9cd830c04b 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -70,10 +70,29 @@ impl Drop for ResourceHolder { #[repr(C)] #[derive(Debug, Clone, Copy)] -struct PushConstants { - color: [f32; 4], - pos: [f32; 2], - scale: [f32; 2], +struct Transform { + matrix: Mat4, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct Mat4 { + m11: f32, + m21: f32, + m31: f32, + m41: f32, + m12: f32, + m22: f32, + m32: f32, + m42: f32, + m13: f32, + m23: f32, + m33: f32, + m43: f32, + m14: f32, + m24: f32, + m34: f32, + m44: f32, } fn run_event_loop() { @@ -201,15 +220,16 @@ fn run_event_loop() { let pipeline_layout = unsafe { use gfx_hal::pso::ShaderStageFlags; - let push_constant_bytes = std::mem::size_of::() as u32; + let push_constant_bytes = std::mem::size_of::() as u32; device .create_pipeline_layout(&[], &[(ShaderStageFlags::VERTEX, 0..push_constant_bytes)]) .expect("Out of memory") }; - let vertex_shader = include_str!("../shaders/triangle.vert"); - let fragment_shader = include_str!("../shaders/triangle.frag"); + // Vertex and fragment shaders for text + let text_vs = include_str!("../shaders/text.vert"); + let text_fs = include_str!("../shaders/text.frag"); /// Create a pipeline with the given layout and shaders. unsafe fn make_pipeline( @@ -284,13 +304,7 @@ fn run_event_loop() { }; let pipeline = unsafe { - make_pipeline::( - &device, - &render_pass, - &pipeline_layout, - vertex_shader, - fragment_shader, - ) + make_pipeline::(&device, &render_pass, &pipeline_layout, text_vs, text_fs) }; let submission_complete_fence = device.create_fence(true).expect("Out of memory"); @@ -368,18 +382,48 @@ fn run_event_loop() { let pipeline_layout = &res.pipeline_layouts[0]; let pipeline = &res.pipelines[0]; - let triangles = text_state.chars().enumerate().map(|(index, char)| { + let triangles = text_state.chars().enumerate().map(|(_index, char)| { if char == ' ' { - PushConstants { - color: [0.0, 0.0, 0.0, 0.0], - pos: [0.0, 0.0], - scale: [0.00, 0.00], + Transform { + matrix: Mat4 { + m11: 0.0, + m21: 0.0, + m31: 0.0, + m41: 0.0, + m12: 0.0, + m22: 0.0, + m32: 0.0, + m42: 0.0, + m13: 0.0, + m23: 0.0, + m33: 0.0, + m43: 0.0, + m14: 0.0, + m24: 0.0, + m34: 0.0, + m44: 0.0, + }, } } else { - PushConstants { - color: [1.0, 1.0, 1.0, 1.0], - pos: [0.06 * index as f32, 0.0], - scale: [0.05, 0.05], + Transform { + matrix: Mat4 { + m11: 0.0, + m21: 0.0, + m31: 0.0, + m41: 0.0, + m12: 0.0, + m22: 0.0, + m32: 0.0, + m42: 0.0, + m13: 0.0, + m23: 0.0, + m33: 0.0, + m43: 0.0, + m14: 0.0, + m24: 0.0, + m34: 0.0, + m44: 0.0, + }, } } }); From b93f129319524df3a6228687b62cc2408c3b4e32 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 27 May 2020 21:23:27 -0400 Subject: [PATCH 06/37] Revert "Add text shaders (segfaults)" This reverts commit 360bb8ec2b580a2b0061854cbd9eb5f1fe2db6e6. --- Cargo.lock | 108 --------------------------------------- editor/Cargo.toml | 1 - editor/shaders/text.frag | 17 ------ editor/shaders/text.vert | 53 ------------------- editor/src/lib.rs | 90 +++++++++----------------------- 5 files changed, 23 insertions(+), 246 deletions(-) delete mode 100644 editor/shaders/text.frag delete mode 100644 editor/shaders/text.vert diff --git a/Cargo.lock b/Cargo.lock index 2dbbf6fd5f..8605000b24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "ab_glyph" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89080d12ceb15b6e4c748e1e0ec048bf97ef28fa20c285dd411738a9d648b08a" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b7e4e8cf778db814365e46839949ca74df4efb10e87ba4913e6ec5967ef0285" - [[package]] name = "adler32" version = "1.0.4" @@ -447,16 +431,6 @@ dependencies = [ "itertools", ] -[[package]] -name = "crossbeam-channel" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" -dependencies = [ - "crossbeam-utils", - "maybe-uninit", -] - [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -790,45 +764,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "glyph_brush" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afd3e2cfd503a5218dd56172a8bf7c8655a4a7cf745737c606a6edfeea1b343f" -dependencies = [ - "glyph_brush_draw_cache", - "glyph_brush_layout", - "log", - "ordered-float", - "rustc-hash", - "twox-hash", -] - -[[package]] -name = "glyph_brush_draw_cache" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588662439c80d2784e6bb1274e2a6378ffcc74f8006b02e67e905039e200764d" -dependencies = [ - "ab_glyph", - "crossbeam-channel", - "crossbeam-deque", - "linked-hash-map", - "rayon", - "rustc-hash", -] - -[[package]] -name = "glyph_brush_layout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa49abf7dcf7bfe68f42c1c8ab7473505aaba14de84afb8899a0109b6c61717" -dependencies = [ - "ab_glyph", - "approx", - "xi-unicode", -] - [[package]] name = "heck" version = "0.3.1" @@ -1060,12 +995,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "linked-hash-map" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" - [[package]] name = "llvm-sys" version = "100.0.1" @@ -1364,15 +1293,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "owned_ttf_parser" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" -dependencies = [ - "ttf-parser", -] - [[package]] name = "parking_lot" version = "0.10.2" @@ -1910,7 +1830,6 @@ dependencies = [ "gfx-backend-vulkan", "gfx-hal", "glsl-to-spirv", - "glyph_brush", "im", "im-rc", "image", @@ -2201,12 +2120,6 @@ dependencies = [ "roc_types", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc_version" version = "0.2.3" @@ -2537,21 +2450,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "ttf-parser" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52fbe7769f5af5d7d25aea74b9443b64e544a5ffb4d2b2968295ddea934f1a06" - -[[package]] -name = "twox-hash" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56" -dependencies = [ - "rand 0.7.3", -] - [[package]] name = "typed-arena" version = "2.0.1" @@ -2898,12 +2796,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" -[[package]] -name = "xi-unicode" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7395cdb9d0a6219fa0ea77d08c946adf9c1984c72fcd443ace30365f3daadef7" - [[package]] name = "xml-rs" version = "0.8.3" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 5a331ea6b3..fdaea372ed 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -50,7 +50,6 @@ inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.releas target-lexicon = "0.10" winit = "0.22" image = "0.23" -glyph_brush="0.7" gfx-hal = "0.5" glsl-to-spirv = "0.1" bincode = "1.2" diff --git a/editor/shaders/text.frag b/editor/shaders/text.frag deleted file mode 100644 index 5492b796fa..0000000000 --- a/editor/shaders/text.frag +++ /dev/null @@ -1,17 +0,0 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -uniform sampler2D font_tex; - -layout(location = 0) in vec2 f_tex_pos; -layout(location = 1) in vec4 f_color; - -layout(location = 0) out vec4 out_color; - -void main() { - float alpha = texture(font_tex, f_tex_pos).r; - if (alpha <= 0.0) { - discard; - } - out_color = f_color * vec4(1.0, 1.0, 1.0, alpha); -} diff --git a/editor/shaders/text.vert b/editor/shaders/text.vert deleted file mode 100644 index 8aaf383746..0000000000 --- a/editor/shaders/text.vert +++ /dev/null @@ -1,53 +0,0 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -const mat4 INVERT_Y_AXIS = mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, -1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) -); - -/* layout(push_constant) uniform Transform { */ -/* mat4 matrix; */ -/* } transform; */ - -layout(location = 0) in vec3 left_top; -layout(location = 1) in vec2 right_bottom; -layout(location = 2) in vec2 tex_left_top; -layout(location = 3) in vec2 tex_right_bottom; -layout(location = 4) in vec4 color; - -layout(location = 0) out vec2 f_tex_pos; -layout(location = 1) out vec4 f_color; - -// generate positional data based on vertex ID -void main() { - vec2 pos = vec2(0.0); - float left = left_top.x; - float right = right_bottom.x; - float top = left_top.y; - float bottom = right_bottom.y; - - switch (gl_VertexIndex) { - case 0: - pos = vec2(left, top); - f_tex_pos = tex_left_top; - break; - case 1: - pos = vec2(right, top); - f_tex_pos = vec2(tex_right_bottom.x, tex_left_top.y); - break; - case 2: - pos = vec2(left, bottom); - f_tex_pos = vec2(tex_left_top.x, tex_right_bottom.y); - break; - case 3: - pos = vec2(right, bottom); - f_tex_pos = tex_right_bottom; - break; - } - - f_color = color; - gl_Position = INVERT_Y_AXIS * /*transform.matrix * */ vec4(pos, left_top.z, 1.0); -} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 9cd830c04b..cff7fe34e6 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -70,29 +70,10 @@ impl Drop for ResourceHolder { #[repr(C)] #[derive(Debug, Clone, Copy)] -struct Transform { - matrix: Mat4, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -struct Mat4 { - m11: f32, - m21: f32, - m31: f32, - m41: f32, - m12: f32, - m22: f32, - m32: f32, - m42: f32, - m13: f32, - m23: f32, - m33: f32, - m43: f32, - m14: f32, - m24: f32, - m34: f32, - m44: f32, +struct PushConstants { + color: [f32; 4], + pos: [f32; 2], + scale: [f32; 2], } fn run_event_loop() { @@ -220,16 +201,15 @@ fn run_event_loop() { let pipeline_layout = unsafe { use gfx_hal::pso::ShaderStageFlags; - let push_constant_bytes = std::mem::size_of::() as u32; + let push_constant_bytes = std::mem::size_of::() as u32; device .create_pipeline_layout(&[], &[(ShaderStageFlags::VERTEX, 0..push_constant_bytes)]) .expect("Out of memory") }; - // Vertex and fragment shaders for text - let text_vs = include_str!("../shaders/text.vert"); - let text_fs = include_str!("../shaders/text.frag"); + let vertex_shader = include_str!("../shaders/triangle.vert"); + let fragment_shader = include_str!("../shaders/triangle.frag"); /// Create a pipeline with the given layout and shaders. unsafe fn make_pipeline( @@ -304,7 +284,13 @@ fn run_event_loop() { }; let pipeline = unsafe { - make_pipeline::(&device, &render_pass, &pipeline_layout, text_vs, text_fs) + make_pipeline::( + &device, + &render_pass, + &pipeline_layout, + vertex_shader, + fragment_shader, + ) }; let submission_complete_fence = device.create_fence(true).expect("Out of memory"); @@ -382,48 +368,18 @@ fn run_event_loop() { let pipeline_layout = &res.pipeline_layouts[0]; let pipeline = &res.pipelines[0]; - let triangles = text_state.chars().enumerate().map(|(_index, char)| { + let triangles = text_state.chars().enumerate().map(|(index, char)| { if char == ' ' { - Transform { - matrix: Mat4 { - m11: 0.0, - m21: 0.0, - m31: 0.0, - m41: 0.0, - m12: 0.0, - m22: 0.0, - m32: 0.0, - m42: 0.0, - m13: 0.0, - m23: 0.0, - m33: 0.0, - m43: 0.0, - m14: 0.0, - m24: 0.0, - m34: 0.0, - m44: 0.0, - }, + PushConstants { + color: [0.0, 0.0, 0.0, 0.0], + pos: [0.0, 0.0], + scale: [0.00, 0.00], } } else { - Transform { - matrix: Mat4 { - m11: 0.0, - m21: 0.0, - m31: 0.0, - m41: 0.0, - m12: 0.0, - m22: 0.0, - m32: 0.0, - m42: 0.0, - m13: 0.0, - m23: 0.0, - m33: 0.0, - m43: 0.0, - m14: 0.0, - m24: 0.0, - m34: 0.0, - m44: 0.0, - }, + PushConstants { + color: [1.0, 1.0, 1.0, 1.0], + pos: [0.06 * index as f32, 0.0], + scale: [0.05, 0.05], } } }); From 5350818e0730c3cbc393a9422c67734ba08e6f31 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 27 May 2020 22:12:37 -0400 Subject: [PATCH 07/37] Default to not animating --- editor/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index cff7fe34e6..5b3e542f85 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -307,7 +307,7 @@ fn run_event_loop() { submission_complete_fence, rendering_complete_semaphore, })); - let is_animating = true; + let is_animating = false; let mut text_state = String::new(); let mut keyboard_modifiers = ModifiersState::empty(); @@ -315,7 +315,7 @@ fn run_event_loop() { use winit::event::{Event, WindowEvent}; use winit::event_loop::ControlFlow; - // TODO dynamically switch this on/off depending on whether any + // TODO dynamically switch is_animating on/off depending on whether any // animations are running. Should conserve CPU usage and battery life! if is_animating { *control_flow = ControlFlow::Poll; From be0f76e3e5c8682bccd705dd84dcf8f316e6faad Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 27 May 2020 22:38:47 -0400 Subject: [PATCH 08/37] Fiddle with default colors a bit --- editor/src/lib.rs | 4 ++-- www/styles.css | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 5b3e542f85..7d15ab2a5b 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -377,7 +377,7 @@ fn run_event_loop() { } } else { PushConstants { - color: [1.0, 1.0, 1.0, 1.0], + color: [0.77254902, 0.658823529, 1.0, 0.5], pos: [0.06 * index as f32, 0.0], scale: [0.05, 0.05], } @@ -486,7 +486,7 @@ fn run_event_loop() { viewport.rect, &[ClearValue { color: ClearColor { - float32: [0.0, 0.0, 0.0, 1.0], + float32: [0.01, 0.01, 0.01, 1.0], }, }], SubpassContents::Inline, diff --git a/www/styles.css b/www/styles.css index 018aa05973..93eb6a8b1d 100644 --- a/www/styles.css +++ b/www/styles.css @@ -1,11 +1,12 @@ :root { --link-color: #5c0bff; + --code-link-color: #4e00eb; --text-color: #333333; --logo-solid: #aaaaaa; --code-color: #222222; + --code-bg-color: #eeeeee; --main-bg-color: #fdfdfd; --border-bg-color: #E9E9E9; - --code-bg-color: #eeeeee; --logo-gradient-start: #aaaaaa; --logo-gradient-mid: #777777; --logo-gradient-end: #333333; @@ -248,11 +249,11 @@ code { } code a { - color: var(--link-color); + color: var(--code-link-color); } code a:visited { - color: var(--link-color); + color: var(--code-link-color); } pre { @@ -294,11 +295,12 @@ pre { --main-bg-color: #363636; --border-bg-color: #111111; --code-color: #eeeeee; - --code-bg-color: #2b2b2b; + --code-bg-color: #2a2a2a; --text-color: #cccccc; --logo-solid: #777777; --faded-color: #bbbbbb; --link-color: #b894ff; + --code-link-color: #c5a8ff; --logo-gradient-start: #333333; --logo-gradient-mid: #777777; --logo-gradient-end: #aaaaaa; From 3b603dc1c4199e8d80743604b663401db64b432a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 28 May 2020 10:33:55 -0400 Subject: [PATCH 09/37] Don't draw spaces at all --- editor/src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 7d15ab2a5b..492138247f 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -368,19 +368,16 @@ fn run_event_loop() { let pipeline_layout = &res.pipeline_layouts[0]; let pipeline = &res.pipelines[0]; - let triangles = text_state.chars().enumerate().map(|(index, char)| { + let triangles = text_state.chars().enumerate().flat_map(|(index, char)| { if char == ' ' { - PushConstants { - color: [0.0, 0.0, 0.0, 0.0], - pos: [0.0, 0.0], - scale: [0.00, 0.00], - } + // Don't render spaces + None } else { - PushConstants { + Some(PushConstants { color: [0.77254902, 0.658823529, 1.0, 0.5], pos: [0.06 * index as f32, 0.0], scale: [0.05, 0.05], - } + }) } }); From bf2f7de69eb999ae83f9b7347c605b41d8e3b352 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 28 May 2020 10:40:27 -0400 Subject: [PATCH 10/37] Extract text_state --- editor/src/lib.rs | 159 +++++---------------------------------- editor/src/text_state.rs | 129 +++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 142 deletions(-) create mode 100644 editor/src/text_state.rs diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 492138247f..92209950b2 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -1,3 +1,4 @@ +use crate::text_state::handle_text_input; use gfx_hal::{ device::Device, window::{Extent2D, PresentMode, PresentationSurface, Surface}, @@ -7,7 +8,9 @@ use glsl_to_spirv::ShaderType; use std::io; use std::mem::ManuallyDrop; use std::path::Path; -use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; +use winit::event::ModifiersState; + +pub mod text_state; /// The editor is actually launched from the CLI if you pass it zero arguments, /// or if you provide it 1 or more files or directories to open on launch. @@ -368,19 +371,6 @@ fn run_event_loop() { let pipeline_layout = &res.pipeline_layouts[0]; let pipeline = &res.pipelines[0]; - let triangles = text_state.chars().enumerate().flat_map(|(index, char)| { - if char == ' ' { - // Don't render spaces - None - } else { - Some(PushConstants { - color: [0.77254902, 0.658823529, 1.0, 0.5], - pos: [0.06 * index as f32, 0.0], - scale: [0.05, 0.05], - }) - } - }); - unsafe { use gfx_hal::pool::CommandPool; @@ -468,6 +458,19 @@ fn run_event_loop() { } }; + let triangles = text_state.chars().enumerate().flat_map(|(index, char)| { + if char == ' ' { + // Don't render spaces + None + } else { + Some(PushConstants { + color: [0.77254902, 0.658823529, 1.0, 0.5], + pos: [0.06 * index as f32, 0.0], + scale: [0.05, 0.05], + }) + } + }); + unsafe { use gfx_hal::command::{ ClearColor, ClearValue, CommandBuffer, CommandBufferFlags, SubpassContents, @@ -554,131 +557,3 @@ unsafe fn push_constant_bytes(push_constants: &T) -> &[u32] { let start_ptr = push_constants as *const T as *const u32; std::slice::from_raw_parts(start_ptr, size_in_u32s) } - -fn handle_text_input( - text_state: &mut String, - elem_state: ElementState, - virtual_keycode: VirtualKeyCode, - _modifiers: ModifiersState, -) { - use winit::event::VirtualKeyCode::*; - - if let ElementState::Released = elem_state { - return; - } - - match virtual_keycode { - Key1 | Numpad1 => text_state.push_str("1"), - Key2 | Numpad2 => text_state.push_str("2"), - Key3 | Numpad3 => text_state.push_str("3"), - Key4 | Numpad4 => text_state.push_str("4"), - Key5 | Numpad5 => text_state.push_str("5"), - Key6 | Numpad6 => text_state.push_str("6"), - Key7 | Numpad7 => text_state.push_str("7"), - Key8 | Numpad8 => text_state.push_str("8"), - Key9 | Numpad9 => text_state.push_str("9"), - Key0 | Numpad0 => text_state.push_str("0"), - A => text_state.push_str("a"), - B => text_state.push_str("b"), - C => text_state.push_str("c"), - D => text_state.push_str("d"), - E => text_state.push_str("e"), - F => text_state.push_str("f"), - G => text_state.push_str("g"), - H => text_state.push_str("h"), - I => text_state.push_str("i"), - J => text_state.push_str("j"), - K => text_state.push_str("k"), - L => text_state.push_str("l"), - M => text_state.push_str("m"), - N => text_state.push_str("n"), - O => text_state.push_str("o"), - P => text_state.push_str("p"), - Q => text_state.push_str("q"), - R => text_state.push_str("r"), - S => text_state.push_str("s"), - T => text_state.push_str("t"), - U => text_state.push_str("u"), - V => text_state.push_str("v"), - W => text_state.push_str("w"), - X => text_state.push_str("x"), - Y => text_state.push_str("y"), - Z => text_state.push_str("z"), - Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15 - | F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause - | Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose - | Caret | Numlock | AbntC1 | AbntC2 | Ax | Calculator | Capital | Convert | Kana - | Kanji | LAlt | LBracket | LControl | LShift | LWin | Mail | MediaSelect | PlayPause - | Power | PrevTrack | MediaStop | Mute | MyComputer | NavigateForward - | NavigateBackward | NextTrack | NoConvert | OEM102 | RAlt | Sysrq | RBracket - | RControl | RShift | RWin | Sleep | Stop | Unlabeled | VolumeDown | VolumeUp | Wake - | WebBack | WebFavorites | WebForward | WebHome | WebRefresh | WebSearch | Apps | Tab - | WebStop => { - // TODO handle - dbg!(virtual_keycode); - } - Back => { - text_state.pop(); - } - Return | NumpadEnter => { - text_state.push_str("\n"); - } - Space => { - text_state.push_str(" "); - } - Comma | NumpadComma => { - text_state.push_str(","); - } - Add => { - text_state.push_str("+"); - } - Apostrophe => { - text_state.push_str("'"); - } - At => { - text_state.push_str("@"); - } - Backslash => { - text_state.push_str("\\"); - } - Colon => { - text_state.push_str(":"); - } - Period | Decimal => { - text_state.push_str("."); - } - Equals | NumpadEquals => { - text_state.push_str("="); - } - Grave => { - text_state.push_str("`"); - } - Minus | Subtract => { - text_state.push_str("-"); - } - Multiply => { - text_state.push_str("*"); - } - Semicolon => { - text_state.push_str(";"); - } - Slash | Divide => { - text_state.push_str("/"); - } - Underline => { - text_state.push_str("_"); - } - Yen => { - text_state.push_str("¥"); - } - Copy => { - todo!("copy"); - } - Paste => { - todo!("paste"); - } - Cut => { - todo!("cut"); - } - } -} diff --git a/editor/src/text_state.rs b/editor/src/text_state.rs new file mode 100644 index 0000000000..1f7e792ab6 --- /dev/null +++ b/editor/src/text_state.rs @@ -0,0 +1,129 @@ +use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; + +pub fn handle_text_input( + text_state: &mut String, + elem_state: ElementState, + virtual_keycode: VirtualKeyCode, + _modifiers: ModifiersState, +) { + use winit::event::VirtualKeyCode::*; + + if let ElementState::Released = elem_state { + return; + } + + match virtual_keycode { + Key1 | Numpad1 => text_state.push_str("1"), + Key2 | Numpad2 => text_state.push_str("2"), + Key3 | Numpad3 => text_state.push_str("3"), + Key4 | Numpad4 => text_state.push_str("4"), + Key5 | Numpad5 => text_state.push_str("5"), + Key6 | Numpad6 => text_state.push_str("6"), + Key7 | Numpad7 => text_state.push_str("7"), + Key8 | Numpad8 => text_state.push_str("8"), + Key9 | Numpad9 => text_state.push_str("9"), + Key0 | Numpad0 => text_state.push_str("0"), + A => text_state.push_str("a"), + B => text_state.push_str("b"), + C => text_state.push_str("c"), + D => text_state.push_str("d"), + E => text_state.push_str("e"), + F => text_state.push_str("f"), + G => text_state.push_str("g"), + H => text_state.push_str("h"), + I => text_state.push_str("i"), + J => text_state.push_str("j"), + K => text_state.push_str("k"), + L => text_state.push_str("l"), + M => text_state.push_str("m"), + N => text_state.push_str("n"), + O => text_state.push_str("o"), + P => text_state.push_str("p"), + Q => text_state.push_str("q"), + R => text_state.push_str("r"), + S => text_state.push_str("s"), + T => text_state.push_str("t"), + U => text_state.push_str("u"), + V => text_state.push_str("v"), + W => text_state.push_str("w"), + X => text_state.push_str("x"), + Y => text_state.push_str("y"), + Z => text_state.push_str("z"), + Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15 + | F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause + | Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose + | Caret | Numlock | AbntC1 | AbntC2 | Ax | Calculator | Capital | Convert | Kana + | Kanji | LAlt | LBracket | LControl | LShift | LWin | Mail | MediaSelect | PlayPause + | Power | PrevTrack | MediaStop | Mute | MyComputer | NavigateForward + | NavigateBackward | NextTrack | NoConvert | OEM102 | RAlt | Sysrq | RBracket + | RControl | RShift | RWin | Sleep | Stop | Unlabeled | VolumeDown | VolumeUp | Wake + | WebBack | WebFavorites | WebForward | WebHome | WebRefresh | WebSearch | Apps | Tab + | WebStop => { + // TODO handle + dbg!(virtual_keycode); + } + Back => { + text_state.pop(); + } + Return | NumpadEnter => { + text_state.push_str("\n"); + } + Space => { + text_state.push_str(" "); + } + Comma | NumpadComma => { + text_state.push_str(","); + } + Add => { + text_state.push_str("+"); + } + Apostrophe => { + text_state.push_str("'"); + } + At => { + text_state.push_str("@"); + } + Backslash => { + text_state.push_str("\\"); + } + Colon => { + text_state.push_str(":"); + } + Period | Decimal => { + text_state.push_str("."); + } + Equals | NumpadEquals => { + text_state.push_str("="); + } + Grave => { + text_state.push_str("`"); + } + Minus | Subtract => { + text_state.push_str("-"); + } + Multiply => { + text_state.push_str("*"); + } + Semicolon => { + text_state.push_str(";"); + } + Slash | Divide => { + text_state.push_str("/"); + } + Underline => { + text_state.push_str("_"); + } + Yen => { + text_state.push_str("¥"); + } + Copy => { + todo!("copy"); + } + Paste => { + todo!("paste"); + } + Cut => { + todo!("cut"); + } + } +} From 2aaec3a808a17195dd5265d9149ba4af8de0d056 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 29 May 2020 13:42:00 -0400 Subject: [PATCH 11/37] Move www/ to other repo --- www/favicon.svg | 4 - www/index.html | 145 ----------------- www/logo.svg | 21 --- www/styles.css | 401 ------------------------------------------------ 4 files changed, 571 deletions(-) delete mode 100644 www/favicon.svg delete mode 100644 www/index.html delete mode 100644 www/logo.svg delete mode 100644 www/styles.css diff --git a/www/favicon.svg b/www/favicon.svg deleted file mode 100644 index e0cff74b57..0000000000 --- a/www/favicon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/www/index.html b/www/index.html deleted file mode 100644 index 35234a94db..0000000000 --- a/www/index.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - The Roc Programming Language - - - - - - - - -
-
- -
-
- -
-
-

Str

-

Dealing with text is a deep topic, so by design, Roc's Str module sticks - to the basics. -

-

For more advanced use cases like working with raw code points, - see the roc/unicode package. For locale-specific text - functions (including capitalizing a string, as capitalization rules vary by locale) - see the roc/locale package.

-

Unicode

-

Unicode can represent text values which span multiple languages, symbols, and emoji. - Here are some valid Roc strings:

-
"Roc"
-"鹏"
-"🐼"
-

Every Unicode string is a sequence of grapheme clusters. - A grapheme cluster corresponds to what a person reading a string might call a "character", - but because the term "character" is used to mean many different concepts across - different programming languages, the documentation for Roc strings intentionally - avoids it. Instead, we use the term "clusters" as a shorthand for "grapheme clusters."

-

You can get the number of grapheme clusters in a string by calling Str.countClusters on it:

-
Str.countClusters "Roc!"   # 4
-Str.countClusters "折り紙"  # 3
-Str.countClusters "🐼"     # 1
-

The countClusters function walks through the entire string to get its answer, - so if you want to check whether a string is empty, you'll get much better performance - by calling Str.isEmpty myStr instead of Str.countClusters myStr == 0.

-

Escape sequences

-

If you put a \ in a Roc string literal, it begins an escape sequence. - An escape sequence is a convenient way to insert certain strings into other strings. - For example, suppose you write this Roc string:

-
"It wasn't a rock\nIt was a rock lobster!"
-

The "\n" in the middle will insert a line break into this string. There are - other ways of getting a line break in there, but "\n" is the most common.

-

Another way you could insert a newline is by writing \u{0x0A} instead of \n. - That would result in the same string, because the \u escape sequence inserts - Unicode code points directly into - the string. The Unicode code point 10 is a newline, and 10 is 0A in hexadecimal. - 0x0A is a Roc hexadecimal literal, and \u escape sequences are always - followed by a hexadecimal literal inside { and } like this.

-

As another example, "R\u{0x6F}c" is the same string as "Roc", because - "\u{0x6F}" corresponds to the Unicode code point for lowercase o. If you - want to spice things up a bit, - you can write "R\u{0xF6}c" as an alternative way to get the string "Röc".

-

Roc strings also support these escape sequences:

-
    -
  • \\ - an actual backslash (writing a single \ always begins an escape sequence!)
  • -
  • \" - an actual quotation mark (writing a " without a \ ends the string)
  • -
  • \r - carriage return
  • -
  • \t - horizontal tab
  • -
  • \v - vertical tab
  • -
-

You can also use escape sequences to insert named strings into other strings, like so:

-
name = "Lee"
-city = "Roctown"
-
-greeting = "Hello, \(name)! Welcome to \(city)."
- -

Here, greeting will become the string "Hello, Lee! Welcome to Roctown.". - This is known as string interpolation, - and you can use it as many times as you like inside a string. The name - between the parentheses must refer to a Str value that is currently in - scope, and it must be a name - it can't be an arbitrary expression like a function call.

-

Encoding

-

Roc strings are not coupled to any particular - encoding. As it happens, - they are currently encoded in UTF-8, but this module is intentionally designed - not to rely on that implementation detail so that a future release of Roc can - potentially change it without breaking existing Roc applications. (UTF-8 - seems pretty great today, but so did UTF-16 at an earlier point in history.)

-

This module has functions to can convert a Str to a List of raw code unit - integers (not to be confused with the code points - mentioned earlier) in a particular encoding. If you need encoding-specific functions, - you should take a look at the roc/unicode package. - It has many more tools than this module does! -

Types

- Str -

A Unicode text value.

-
-
-
-
-

Made by people who like to make nice things.

-

© 2020-present

-
- - diff --git a/www/logo.svg b/www/logo.svg deleted file mode 100644 index 8cdc52a7d3..0000000000 --- a/www/logo.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/www/styles.css b/www/styles.css deleted file mode 100644 index 93eb6a8b1d..0000000000 --- a/www/styles.css +++ /dev/null @@ -1,401 +0,0 @@ -:root { - --link-color: #5c0bff; - --code-link-color: #4e00eb; - --text-color: #333333; - --logo-solid: #aaaaaa; - --code-color: #222222; - --code-bg-color: #eeeeee; - --main-bg-color: #fdfdfd; - --border-bg-color: #E9E9E9; - --logo-gradient-start: #aaaaaa; - --logo-gradient-mid: #777777; - --logo-gradient-end: #333333; - --faded-color: #4C4C4C; - --monospace-font; - --font-sans: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; - --font-mono: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; - --sidebar-width: 240px; -} - -a { - color: #972395; -} - -.top-header { - box-sizing: border-box; - flex-direction: row; - align-items: center; - display: flex; - font-family: var(--font-sans); - font-size: 24px; -} - -.main-nav { - display: flex; - flex-direction: row; - align-items: center; - flex-wrap: nowrap; - flex-grow: 1; - box-sizing: border-box; - padding: 6px 0; - min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */ -} - -.logo { - padding-left: 16px; - padding-right: 8px; -} - -.logo svg { - height: 48px; - width: 48px; -} - -.logo:hover { - text-decoration: none; -} - -.logo-solid { - fill: url("#logo-gradient") -} - -.logo:hover .logo-solid { - fill: var(--link-color); -} - -.pkg-full-name { - color: var(--text-color); - display: inline-block; - font-size: 32px; - margin: 0 18px; - font-weight: normal; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - padding-bottom: 8px; -} - -.pkg-full-name a { - color: inherit; -} - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -.pkg-and-logo { - min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */ - display: flex; - align-items: flex-end; - height: 55px; -} - -.main-container { - min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */ -} - -.search-button { - flex-shrink: 0; /* always shrink the package name before these; they have a relatively constrained length */ - padding: 12px 18px; - margin-right: 42px; - display: none; /* only show this in the mobile view */ -} - -.version { - font-weight: bold; - padding: 10px; - margin-right: 8px; - color: var(--faded-color); -} - -.container { - box-sizing: border-box; - display: flex; - flex-direction: column; - margin: 0 auto; - min-width: 780px; - max-width: 1280px; -} - -body { - box-sizing: border-box; - margin: 0; - padding: 0; - font-family: var(--font-sans); - color: var(--text-color); - background-color: var(--border-bg-color); -} - -p { - overflow-wrap: break-word; - margin: 24px 0; -} - -footer { - font-size: 14px; - box-sizing: border-box; - padding: 16px; -} - -footer p { - display: inline-block; - margin-top: 0; - margin-bottom: 8px; -} - -.content { - box-sizing: border-box; - display: flex; - flex-direction: row-reverse; /* We want the sidebar in the DOM first for tab ordering, but it should render on the right. */ - justify-content: space-between; -} - -main { - box-sizing: border-box; - position: relative; - font-size: 18px; - line-height: 1.85em; - padding: 32px; - background-color: var(--main-bg-color); - clip-path: polygon(0 100%, 100% 100%, 100% 48px, calc(100% - 48px) 0, 0px 0px); -} - -.sidebar { - position: relative; - box-sizing: border-box; - padding-left: 20px; - width: var(--sidebar-width); - margin-top: 128px; - flex-shrink: 0; -} - -#sidebar-heading { - font-size: 32px; - font-weight: normal; - margin: 0; - padding: 0; - color: var(--faded-color); - font-family: var(--font-sans); - cursor: default; -} - -.module-name { - font-size: 56px; - line-height: 1em; - font-family: var(--font-mono); - font-weight: bold; - margin-top: 18px; - margin-bottom: 48px; -} - -.module-name a:visited { - color: var(--link-color); -} - -.sidebar-link { - box-sizing: border-box; - font-size: 18px; - font-family: var(--font-mono); - font-weight: bold; - color: var(--faded-color); - display: block; - width: 100%; - padding: 8px 16px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -a { - color: var(--link-color); -} - -a:visited { - color: inherit; -} - -h3 { - font-size: 32px; - margin: 48px 0 24px 0; -} - -h4 { - font-size: 24px; -} - -.type-def { - font-size: 24px; - color: var(--link-color); -} - -.code-snippet { - padding: 12px 16px; - display: block; - box-sizing: border-box; - font-family: var(--font-mono); - background-color: var(--border-bg-color); -} - -code { - font-family: var(--font-mono); - color: var(--code-color); - background-color: var(--code-bg-color); - padding: 0 8px; - display: inline-block; -} - -code a { - color: var(--code-link-color); -} - -code a:visited { - color: var(--code-link-color); -} - -pre { - margin: 36px 0; - padding: 8px; - box-sizing: border-box; - background-color: var(--code-bg-color); - overflow-x: auto; -} - -#module-search { - width: 100%; - font-family: var(--font-mono); - display: block; - box-sizing: border-box; - font-size: 18px; - margin: 12px 0; - padding: 12px 16px; - border: none; - color: var(--faded-color); - background: none; -} - -#module-search::placeholder { - color: var(--faded-color); - opacity: 1; -} - -#module-search:hover { - background-color: var(--main-bg-color); -} - -#module-search:focus { - background-color: var(--main-bg-color); -} - -@media (prefers-color-scheme: dark) { - :root { - --main-bg-color: #363636; - --border-bg-color: #111111; - --code-color: #eeeeee; - --code-bg-color: #2a2a2a; - --text-color: #cccccc; - --logo-solid: #777777; - --faded-color: #bbbbbb; - --link-color: #b894ff; - --code-link-color: #c5a8ff; - --logo-gradient-start: #333333; - --logo-gradient-mid: #777777; - --logo-gradient-end: #aaaaaa; - } - - html { - scrollbar-color: #444444 #2f2f2f; - } -} - -@media only screen and (max-device-width: 480px) { - .search-button { - display: block; /* This is only visible in mobile. */ - } - - .top-header { - width: auto; - } - - .pkg-full-name { - margin-left: 8px; - margin-right: 12px; - font-size: 24px; - padding-bottom: 14px; - } - - .pkg-full-name a { - vertical-align: middle; - padding: 18px 0; - } - - .logo { - padding-left: 2px; - width: 50px; - height: 54px; - } - - .version { - margin: 0; - font-weight: normal; - font-size: 18px; - padding-bottom: 16px; - } - - .module-name { - font-size: 36px; - margin-top: 8px; - margin-bottom: 8px; - max-width: calc(100% - 18px); - overflow: hidden; - text-overflow: ellipsis; - } - - main { - padding: 18px; - font-size: 16px; - } - - .container { - margin: 0; - min-width: 320px; - max-width: 100%; - } - - .content { - flex-direction: column; - } - - .sidebar { - margin-top: 0; - padding-left: 0; - width: auto; - } - - #sidebar-heading { - font-size: 24px; - margin: 16px; - } - - h3 { - font-size: 18px; - margin: 0; - padding: 0; - } - - h4 { - font-size: 16px; - } - - .main-nav { - justify-content: space-between; - } - - .content { - /* Display the sidebar below
without affecting tab index */ - flex-direction: column-reverse; - } -} From 195c695ab12e67ef6f5e48e689aa1c80e262486e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 29 May 2020 22:57:26 -0400 Subject: [PATCH 12/37] wip w/ sampler and dots --- editor/creature.png | Bin 0 -> 113314 bytes editor/src/lib.rs | 408 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 359 insertions(+), 49 deletions(-) create mode 100644 editor/creature.png diff --git a/editor/creature.png b/editor/creature.png new file mode 100644 index 0000000000000000000000000000000000000000..f0fd87e6b0c995d9a8522b054a56b62f71cca425 GIT binary patch literal 113314 zcmV(wK zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3^ilI%FLUH>_XI|SlEfEW&5G}8<;e658zUEN)3 zT{Fqlr|VQ!W@H2jxNqOv;qJWl|NI}{^U!mj}ul&@vv{G*Vms@VJ^Tm7r{`dQL zxbyk@{O@`1_~*O-_WkRxA4I;D_;>pKpzPnjL+AU?KYyX-Ki?n!{{5@Y-xu-y^G1Ju z@Q*i4ezG6+=XLL&H%jsM{qX0%ulDEndj6N&`SZ`*zpD*@-t_PPohys69=@646?fwM z{9XK%#1*8L`uTVFzoTD0eEp=v-#Nwqymr@LU;b_J{`%@~Yj^iQ-%tO(6xUt;yrBGT zjsAQ;JpXy^&IO~x&GYLKW^oG#R~^Z zna-~j{#5>X_`04yjXy89IN7nqZVvv;E3^<e`9k>gI9ZI-sj5P$-@nwHHR~}Q2gB=LZ z6IZxC_9msm=Dg9*=67m1uZ>UqeH&FZGhWeA9d_B)op8C8` zdwNMFm#36cix|~ws<}S3)LP%#YJbz4-{&oFec!jey~UP*u&0$)ulBaqdgntsZ{7Lr z&MSH!aio#QGs>vr9c}c9`OG|@S!SK@Y_qSp(!%{$S@l})YOC+CF{PdNv&*jg-EH>| zTl>xVBU+r7` zKkeM**8Tt3xy!BlvvdFZzWs+?Tm3|obT&vYbWH(4fbA>c{ho)9FX7{ZbG*OaX5r>O zaq?sBd)i#+J<4LWXId-w5n}ynjj7_wVm9BYDR6_~>GO@mU%&q1eqQdr`vy2--UUPik72;gAe89ra_cvh$EJ6N8>U**NUg67cIGFPkLt}65U_sSD!rID~&aIqn=ow zycg><*M9rjbL9Dcm8H06>SNTHxsuL>^=}sA0M_?@eXvV!&KP83@i39aE{ApR`r#{2 zeCpr>?#inle)h1LW87`;mF66a-C~E+WMwPfwj034*6!8UhXuWNO7)vxZg}wrn%llZ`{LvW zEApdfj#S5D<-Zu79JsA(1-$kh?-#+0lJLXX{nZJO?8e(Z?@HVpp2QNB4RTbjHDhBz z^INMu%eeO3W3qr>e}PPX)Sef!Zh5CR2?S4B8<&i$r#VjK2z%q+xvX69#T%1K5P^RA z>3Bd}p}d>7{#vVp@XeyF#aeezgD*wlvL!JeHDk}wSpj-*V#$ppzvkJ>td#{^NwGtozg&A%0A(Joj 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 io::Result<()> { // TODO do any initialization here - run_event_loop(); + run_event_loop().expect("Error running event loop"); Ok(()) } use winit::{event_loop::EventLoop, window::WindowBuilder}; -struct Resources { +struct Resources { instance: B::Instance, surface: B::Surface, device: B::Device, @@ -34,6 +52,8 @@ struct Resources { command_pool: B::CommandPool, submission_complete_fence: B::Fence, rendering_complete_semaphore: B::Semaphore, + vertices: BufferBundle, + indexes: BufferBundle, } struct ResourceHolder(ManuallyDrop>); @@ -49,6 +69,8 @@ impl Drop for ResourceHolder { render_passes, pipeline_layouts, pipelines, + indexes, + vertices, submission_complete_fence, rendering_complete_semaphore, } = ManuallyDrop::take(&mut self.0); @@ -71,6 +93,326 @@ impl Drop for ResourceHolder { } } +pub const VERTEX_SOURCE: &str = "#version 450 +layout (location = 0) in vec2 position; +layout (location = 1) in vec3 color; +layout (location = 2) in vec2 vert_uv; +layout (location = 0) out gl_PerVertex { + vec4 gl_Position; +}; +layout (location = 1) out vec3 frag_color; +layout (location = 2) out vec2 frag_uv; +void main() +{ + gl_Position = vec4(position, 0.0, 1.0); + frag_color = color; + frag_uv = vert_uv; +}"; + +pub const FRAGMENT_SOURCE: &str = "#version 450 +layout (push_constant) uniform PushConsts { + float time; +} push; +layout(set = 0, binding = 0) uniform texture2D tex; +layout(set = 0, binding = 1) uniform sampler samp; +layout (location = 1) in vec3 frag_color; +layout (location = 2) in vec2 frag_uv; +layout (location = 0) out vec4 color; +void main() +{ + float time01 = -0.9 * abs(sin(push.time * 0.7)) + 0.9; + vec4 tex_color = texture(sampler2D(tex, samp), frag_uv); + color = mix(tex_color, vec4(frag_color, 1.0), time01); +}"; + +pub static CREATURE_BYTES: &[u8] = include_bytes!("../creature.png"); + +#[derive(Debug, Clone, Copy)] +pub struct Quad { + pub x: f32, + pub y: f32, + pub w: f32, + pub h: f32, +} +impl Quad { + pub fn vertex_attributes(self) -> [f32; 4 * (2 + 3 + 2)] { + let x = self.x; + let y = self.y; + let w = self.w; + let h = self.h; + #[cfg_attr(rustfmt, rustfmt_skip)] + [ + // X Y R G B U V + x , y+h, 1.0, 0.0, 0.0, /* red */ 0.0, 1.0, /* bottom left */ + x , y , 0.0, 1.0, 0.0, /* green */ 0.0, 0.0, /* top left */ + x+w, y , 0.0, 0.0, 1.0, /* blue */ 1.0, 0.0, /* bottom right */ + x+w, y+h, 1.0, 0.0, 1.0, /* magenta */ 1.0, 1.0, /* top right */ + ] + } +} + +pub struct BufferBundle> { + pub buffer: ManuallyDrop, + pub requirements: Requirements, + pub memory: ManuallyDrop, + pub phantom: PhantomData, +} +impl> BufferBundle { + pub fn new( + adapter: &Adapter, + device: &D, + size: usize, + usage: BufferUsage, + ) -> Result { + unsafe { + let mut buffer = device + .create_buffer(size as u64, usage) + .map_err(|_| "Couldn't create a buffer!")?; + let requirements = device.get_buffer_requirements(&buffer); + let memory_type_id = adapter + .physical_device + .memory_properties() + .memory_types + .iter() + .enumerate() + .find(|&(id, memory_type)| { + requirements.type_mask & (1 << id) != 0 + && memory_type.properties.contains(Properties::CPU_VISIBLE) + }) + .map(|(id, _)| MemoryTypeId(id)) + .ok_or("Couldn't find a memory type to support the buffer!")?; + let memory = device + .allocate_memory(memory_type_id, requirements.size) + .map_err(|_| "Couldn't allocate buffer memory!")?; + device + .bind_buffer_memory(&memory, 0, &mut buffer) + .map_err(|_| "Couldn't bind the buffer memory!")?; + Ok(Self { + buffer: ManuallyDrop::new(buffer), + requirements, + memory: ManuallyDrop::new(memory), + phantom: PhantomData, + }) + } + } + + pub unsafe fn manually_drop(&self, device: &D) { + device.destroy_buffer(ManuallyDrop::into_inner(read(&self.buffer))); + device.free_memory(ManuallyDrop::into_inner(read(&self.memory))); + } +} + +pub struct LoadedImage { + pub image: ManuallyDrop, + pub requirements: Requirements, + pub memory: ManuallyDrop, + pub image_view: ManuallyDrop, + pub sampler: ManuallyDrop, + pub phantom: PhantomData, +} + +impl LoadedImage { + pub fn new( + adapter: &Adapter, + device: &B::Device, + command_pool: &mut B::CommandPool, + command_queue: &mut B::CommandQueue, + img: image::RgbaImage, + ) -> Result { + unsafe { + // 0. First we compute some memory related values. + let pixel_size = size_of::>(); + let row_size = pixel_size * (img.width() as usize); + let limits = adapter.physical_device.limits(); + let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; + let row_pitch = ((row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; + debug_assert!(row_pitch as usize >= row_size); + + // 1. make a staging buffer with enough memory for the image, and a + // transfer_src usage + let required_bytes = row_pitch * img.height() as usize; + let staging_bundle = + BufferBundle::new(&adapter, device, required_bytes, BufferUsage::TRANSFER_SRC)?; + + // 2. use mapping writer to put the image data into that buffer + unsafe { + let mapping = device + .map_memory(&staging_bundle.memory, Segment::ALL) + .unwrap(); + + for y in 0..img.height() as usize { + let row = &(*img)[y * row_size..(y + 1) * row_size]; + let dest_base = y * row_pitch; + + mapping[dest_base..dest_base + row.len()].copy_from_slice(row); + } + + device.unmap_memory(&staging_bundle.memory); + } + + // 3. Make an image with transfer_dst and SAMPLED usage + let mut the_image = device + .create_image( + gfx_hal::image::Kind::D2(img.width(), img.height(), 1, 1), + 1, + Format::Rgba8Srgb, + gfx_hal::image::Tiling::Optimal, + gfx_hal::image::Usage::TRANSFER_DST | gfx_hal::image::Usage::SAMPLED, + gfx_hal::image::ViewCapabilities::empty(), + ) + .map_err(|_| "Couldn't create the image!")?; + + // 4. allocate memory for the image and bind it + let requirements = device.get_image_requirements(&the_image); + let memory_type_id = adapter + .physical_device + .memory_properties() + .memory_types + .iter() + .enumerate() + .find(|&(id, memory_type)| { + // BIG NOTE: THIS IS DEVICE LOCAL NOT CPU VISIBLE + requirements.type_mask & (1 << id) != 0 + && memory_type.properties.contains(Properties::DEVICE_LOCAL) + }) + .map(|(id, _)| MemoryTypeId(id)) + .ok_or("Couldn't find a memory type to support the image!")?; + let memory = device + .allocate_memory(memory_type_id, requirements.size) + .map_err(|_| "Couldn't allocate image memory!")?; + device + .bind_image_memory(&memory, 0, &mut the_image) + .map_err(|_| "Couldn't bind the image memory!")?; + + // 5. create image view and sampler + let image_view = device + .create_image_view( + &the_image, + gfx_hal::image::ViewKind::D2, + Format::Rgba8Srgb, + gfx_hal::format::Swizzle::NO, + SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + ) + .map_err(|_| "Couldn't create the image view!")?; + let sampler = device + .create_sampler(gfx_hal::image::SamplerInfo::new( + gfx_hal::image::Filter::Nearest, + gfx_hal::image::WrapMode::Tile, + )) + .map_err(|_| "Couldn't create the sampler!")?; + + // 6. create a command buffer + let mut cmd_buffer = command_pool.allocate_one(Level::Primary); + cmd_buffer.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); + + // 7. Use a pipeline barrier to transition the image from empty/undefined + // to TRANSFER_WRITE/TransferDstOptimal + let image_barrier = gfx_hal::memory::Barrier::Image { + states: (gfx_hal::image::Access::empty(), Layout::Undefined) + ..( + gfx_hal::image::Access::TRANSFER_WRITE, + Layout::TransferDstOptimal, + ), + target: &the_image, + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + }; + cmd_buffer.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + gfx_hal::memory::Dependencies::empty(), + &[image_barrier], + ); + + // 8. perform copy from staging buffer to image + cmd_buffer.copy_buffer_to_image( + &staging_bundle.buffer, + &the_image, + Layout::TransferDstOptimal, + &[gfx_hal::command::BufferImageCopy { + buffer_offset: 0, + buffer_width: (row_pitch / pixel_size) as u32, + buffer_height: img.height(), + image_layers: gfx_hal::image::SubresourceLayers { + aspects: Aspects::COLOR, + level: 0, + layers: 0..1, + }, + image_offset: gfx_hal::image::Offset { x: 0, y: 0, z: 0 }, + image_extent: gfx_hal::image::Extent { + width: img.width(), + height: img.height(), + depth: 1, + }, + }], + ); + + // 9. use pipeline barrier to transition the image to SHADER_READ access/ + // ShaderReadOnlyOptimal layout + let image_barrier = gfx_hal::memory::Barrier::Image { + states: ( + gfx_hal::image::Access::TRANSFER_WRITE, + Layout::TransferDstOptimal, + ) + ..( + gfx_hal::image::Access::SHADER_READ, + Layout::ShaderReadOnlyOptimal, + ), + target: &the_image, + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + }; + cmd_buffer.pipeline_barrier( + PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER, + gfx_hal::memory::Dependencies::empty(), + &[image_barrier], + ); + + // 10. Submit the cmd buffer to queue and wait for it + cmd_buffer.finish(); + let upload_fence = device + .create_fence(false) + .map_err(|_| "Couldn't create an upload fence!")?; + command_queue.submit_without_semaphores(Some(&cmd_buffer), Some(&upload_fence)); + device + .wait_for_fence(&upload_fence, core::u64::MAX) + .map_err(|_| "Couldn't wait for the fence!")?; + device.destroy_fence(upload_fence); + + // 11. Destroy the staging bundle and one shot buffer now that we're done + staging_bundle.manually_drop(device); + command_pool.free(Some(cmd_buffer)); + + Ok(Self { + image: ManuallyDrop::new(the_image), + requirements, + memory: ManuallyDrop::new(memory), + image_view: ManuallyDrop::new(image_view), + sampler: ManuallyDrop::new(sampler), + phantom: PhantomData, + }) + } + } + + pub unsafe fn manually_drop(&self, device: &B::Device) { + device.destroy_sampler(ManuallyDrop::into_inner(read(&self.sampler))); + device.destroy_image_view(ManuallyDrop::into_inner(read(&self.image_view))); + device.destroy_image(ManuallyDrop::into_inner(read(&self.image))); + device.free_memory(ManuallyDrop::into_inner(read(&self.memory))); + } +} + #[repr(C)] #[derive(Debug, Clone, Copy)] struct PushConstants { @@ -79,7 +421,7 @@ struct PushConstants { scale: [f32; 2], } -fn run_event_loop() { +fn run_event_loop() -> Result<(), &'static str> { // TODO do a better window size const WINDOW_SIZE: [u32; 2] = [512, 512]; @@ -122,8 +464,6 @@ fn run_event_loop() { }; let (device, mut queue_group) = { - use gfx_hal::queue::QueueFamily; - let queue_family = adapter .queue_families .iter() @@ -133,8 +473,6 @@ fn run_event_loop() { .expect("No compatible queue family found"); let mut gpu = unsafe { - use gfx_hal::adapter::PhysicalDevice; - adapter .physical_device .open(&[(queue_family, &[1.0])], gfx_hal::Features::empty()) @@ -145,9 +483,6 @@ fn run_event_loop() { }; let (command_pool, mut command_buffer) = unsafe { - use gfx_hal::command::Level; - use gfx_hal::pool::{CommandPool, CommandPoolCreateFlags}; - let mut command_pool = device .create_command_pool(queue_group.family, CommandPoolCreateFlags::empty()) .expect("Out of memory"); @@ -158,8 +493,6 @@ fn run_event_loop() { }; let surface_color_format = { - use gfx_hal::format::{ChannelType, Format}; - let supported_formats = surface .supported_formats(&adapter.physical_device) .unwrap_or_else(|| vec![]); @@ -173,11 +506,6 @@ fn run_event_loop() { }; let render_pass = { - use gfx_hal::image::Layout; - use gfx_hal::pass::{ - Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp, SubpassDesc, - }; - let color_attachment = Attachment { format: Some(surface_color_format), samples: 1, @@ -202,8 +530,6 @@ fn run_event_loop() { }; let pipeline_layout = unsafe { - use gfx_hal::pso::ShaderStageFlags; - let push_constant_bytes = std::mem::size_of::() as u32; device @@ -221,13 +547,7 @@ fn run_event_loop() { pipeline_layout: &B::PipelineLayout, vertex_shader: &str, fragment_shader: &str, - ) -> B::GraphicsPipeline { - use gfx_hal::pass::Subpass; - use gfx_hal::pso::{ - BlendState, ColorBlendDesc, ColorMask, EntryPoint, Face, GraphicsPipelineDesc, - GraphicsShaderSet, Primitive, Rasterizer, Specialization, - }; - + ) -> Result { let vertex_shader_module = device .create_shader_module(&compile_shader(vertex_shader, ShaderType::Vertex)) .expect("Failed to create vertex shader module"); @@ -283,7 +603,7 @@ fn run_event_loop() { device.destroy_shader_module(vertex_shader_module); device.destroy_shader_module(fragment_shader_module); - pipeline + Ok(pipeline) }; let pipeline = unsafe { @@ -294,10 +614,16 @@ fn run_event_loop() { vertex_shader, fragment_shader, ) - }; + }?; let submission_complete_fence = device.create_fence(true).expect("Out of memory"); let rendering_complete_semaphore = device.create_semaphore().expect("Out of memory"); + + const F32_XY_RGB_UV_QUAD: usize = size_of::() * (2 + 3 + 2) * 4; + let vertices = BufferBundle::new(&adapter, &device, F32_XY_RGB_UV_QUAD, BufferUsage::VERTEX)?; + + const U16_QUAD_INDICES: usize = size_of::() * 2 * 3; + let indexes = BufferBundle::new(&adapter, &device, U16_QUAD_INDICES, BufferUsage::INDEX)?; let mut resource_holder: ResourceHolder = ResourceHolder(ManuallyDrop::new(Resources { instance, @@ -309,6 +635,8 @@ fn run_event_loop() { pipelines: vec![pipeline], submission_complete_fence, rendering_complete_semaphore, + vertices, + indexes, })); let is_animating = false; let mut text_state = String::new(); @@ -372,8 +700,6 @@ fn run_event_loop() { let pipeline = &res.pipelines[0]; unsafe { - use gfx_hal::pool::CommandPool; - // We refuse to wait more than a second, to avoid hanging. let render_timeout_ns = 1_000_000_000; @@ -389,8 +715,6 @@ fn run_event_loop() { } if should_configure_swapchain { - use gfx_hal::window::SwapchainConfig; - let caps = res.surface.capabilities(&adapter.physical_device); let mut swapchain_config = @@ -427,10 +751,6 @@ fn run_event_loop() { }; let framebuffer = unsafe { - use std::borrow::Borrow; - - use gfx_hal::image::Extent; - res.device .create_framebuffer( render_pass, @@ -445,8 +765,6 @@ fn run_event_loop() { }; let viewport = { - use gfx_hal::pso::{Rect, Viewport}; - Viewport { rect: Rect { x: 0, @@ -472,10 +790,6 @@ fn run_event_loop() { }); unsafe { - use gfx_hal::command::{ - ClearColor, ClearValue, CommandBuffer, CommandBufferFlags, SubpassContents, - }; - command_buffer.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); command_buffer.set_viewports(0, &[viewport.clone()]); @@ -494,8 +808,6 @@ fn run_event_loop() { command_buffer.bind_graphics_pipeline(pipeline); for triangle in triangles { - use gfx_hal::pso::ShaderStageFlags; - command_buffer.push_graphics_constants( pipeline_layout, ShaderStageFlags::VERTEX, @@ -511,8 +823,6 @@ fn run_event_loop() { } unsafe { - use gfx_hal::queue::{CommandQueue, Submission}; - let submission = Submission { command_buffers: vec![&command_buffer], wait_semaphores: None, From 79469b2d2305c348602b1589e4a5f465a3cce387 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 29 May 2020 23:23:30 -0400 Subject: [PATCH 13/37] wip try merging in glyph stuff --- Cargo.lock | 108 ++++++++++++++++ editor/Cargo.toml | 1 + editor/src/lib.rs | 304 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 409 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8605000b24..efdb021c24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "ab_glyph" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d4cab67fcb3051906d685028ad5e846bad471a6d468943c6cbcc498a8a33559" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7e4e8cf778db814365e46839949ca74df4efb10e87ba4913e6ec5967ef0285" + [[package]] name = "adler32" version = "1.0.4" @@ -431,6 +447,16 @@ dependencies = [ "itertools", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" +dependencies = [ + "crossbeam-utils", + "maybe-uninit", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -764,6 +790,45 @@ dependencies = [ "tempfile", ] +[[package]] +name = "glyph_brush" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afd3e2cfd503a5218dd56172a8bf7c8655a4a7cf745737c606a6edfeea1b343f" +dependencies = [ + "glyph_brush_draw_cache", + "glyph_brush_layout", + "log", + "ordered-float", + "rustc-hash", + "twox-hash", +] + +[[package]] +name = "glyph_brush_draw_cache" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588662439c80d2784e6bb1274e2a6378ffcc74f8006b02e67e905039e200764d" +dependencies = [ + "ab_glyph", + "crossbeam-channel", + "crossbeam-deque", + "linked-hash-map", + "rayon", + "rustc-hash", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa49abf7dcf7bfe68f42c1c8ab7473505aaba14de84afb8899a0109b6c61717" +dependencies = [ + "ab_glyph", + "approx", + "xi-unicode", +] + [[package]] name = "heck" version = "0.3.1" @@ -995,6 +1060,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + [[package]] name = "llvm-sys" version = "100.0.1" @@ -1293,6 +1364,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "owned_ttf_parser" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" +dependencies = [ + "ttf-parser", +] + [[package]] name = "parking_lot" version = "0.10.2" @@ -1830,6 +1910,7 @@ dependencies = [ "gfx-backend-vulkan", "gfx-hal", "glsl-to-spirv", + "glyph_brush", "im", "im-rc", "image", @@ -2120,6 +2201,12 @@ dependencies = [ "roc_types", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2450,6 +2537,21 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "ttf-parser" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52fbe7769f5af5d7d25aea74b9443b64e544a5ffb4d2b2968295ddea934f1a06" + +[[package]] +name = "twox-hash" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56" +dependencies = [ + "rand 0.7.3", +] + [[package]] name = "typed-arena" version = "2.0.1" @@ -2796,6 +2898,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" +[[package]] +name = "xi-unicode" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7395cdb9d0a6219fa0ea77d08c946adf9c1984c72fcd443ace30365f3daadef7" + [[package]] name = "xml-rs" version = "0.8.3" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index fdaea372ed..b13a558fb1 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -54,6 +54,7 @@ gfx-hal = "0.5" glsl-to-spirv = "0.1" bincode = "1.2" serde = { version = "1.0", features = ["derive"] } +glyph_brush = "0.7" [target.'cfg(target_os = "macos")'.dependencies.backend] package = "gfx-backend-metal" diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 4e6512781a..8f3c15e2c9 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -1,7 +1,5 @@ use crate::text_state::handle_text_input; use core::ptr::read; -use gfx_hal::pso::ShaderStageFlags; -use gfx_hal::pso::{Rect, Viewport}; use gfx_hal::{ adapter::{Adapter, PhysicalDevice}, buffer::Usage as BufferUsage, @@ -14,13 +12,15 @@ use gfx_hal::{ pool::{CommandPool, CommandPoolCreateFlags}, pso::{ BlendState, ColorBlendDesc, ColorMask, EntryPoint, Face, GraphicsPipelineDesc, - GraphicsShaderSet, PipelineStage, Primitive, Rasterizer, Specialization, + GraphicsShaderSet, PipelineStage, Primitive, Rasterizer, ShaderStageFlags, Specialization, + Viewport, }, queue::{CommandQueue, QueueFamily, Submission}, window::{Extent2D, PresentMode, PresentationSurface, Surface, SwapchainConfig}, Backend, Instance, MemoryTypeId, }; use glsl_to_spirv::ShaderType; +use glyph_brush::ab_glyph::{point, Rect}; use std::borrow::Borrow; use std::io; use std::marker::PhantomData; @@ -421,6 +421,63 @@ struct PushConstants { scale: [f32; 2], } +pub type Vertex = [f32; 13]; + +#[inline] +pub fn to_vertex( + glyph_brush::GlyphVertex { + mut tex_coords, + pixel_coords, + bounds, + extra, + }: glyph_brush::GlyphVertex, +) -> Vertex { + let gl_bounds = bounds; + + let mut gl_rect = Rect { + min: point(pixel_coords.min.x as f32, pixel_coords.min.y as f32), + max: point(pixel_coords.max.x as f32, pixel_coords.max.y as f32), + }; + + // handle overlapping bounds, modify uv_rect to preserve texture aspect + if gl_rect.max.x > gl_bounds.max.x { + let old_width = gl_rect.width(); + gl_rect.max.x = gl_bounds.max.x; + tex_coords.max.x = tex_coords.min.x + tex_coords.width() * gl_rect.width() / old_width; + } + if gl_rect.min.x < gl_bounds.min.x { + let old_width = gl_rect.width(); + gl_rect.min.x = gl_bounds.min.x; + tex_coords.min.x = tex_coords.max.x - tex_coords.width() * gl_rect.width() / old_width; + } + if gl_rect.max.y > gl_bounds.max.y { + let old_height = gl_rect.height(); + gl_rect.max.y = gl_bounds.max.y; + tex_coords.max.y = tex_coords.min.y + tex_coords.height() * gl_rect.height() / old_height; + } + if gl_rect.min.y < gl_bounds.min.y { + let old_height = gl_rect.height(); + gl_rect.min.y = gl_bounds.min.y; + tex_coords.min.y = tex_coords.max.y - tex_coords.height() * gl_rect.height() / old_height; + } + + [ + gl_rect.min.x, + gl_rect.max.y, + extra.z, + gl_rect.max.x, + gl_rect.min.y, + tex_coords.min.x, + tex_coords.max.y, + tex_coords.max.x, + tex_coords.min.y, + extra.color[0], + extra.color[1], + extra.color[2], + extra.color[3], + ] +} + fn run_event_loop() -> Result<(), &'static str> { // TODO do a better window size const WINDOW_SIZE: [u32; 2] = [512, 512]; @@ -766,7 +823,7 @@ fn run_event_loop() -> Result<(), &'static str> { let viewport = { Viewport { - rect: Rect { + rect: gfx_hal::pso::Rect { x: 0, y: 0, w: surface_extent.width as i16, @@ -867,3 +924,242 @@ unsafe fn push_constant_bytes(push_constants: &T) -> &[u32] { let start_ptr = push_constants as *const T as *const u32; std::slice::from_raw_parts(start_ptr, size_in_u32s) } + +// TEXT + +pub fn compile_shader(src: &str, ty: GLenum) -> Res { + let shader; + unsafe { + shader = gl::CreateShader(ty); + // Attempt to compile the shader + let c_str = CString::new(src.as_bytes())?; + gl::ShaderSource(shader, 1, &c_str.as_ptr(), ptr::null()); + gl::CompileShader(shader); + + // Get the compile status + let mut status = GLint::from(gl::FALSE); + gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status); + + // Fail on error + if status != GLint::from(gl::TRUE) { + let mut len = 0; + gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len); + let mut buf = Vec::with_capacity(len as usize); + buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character + gl::GetShaderInfoLog( + shader, + len, + ptr::null_mut(), + buf.as_mut_ptr() as *mut GLchar, + ); + return Err(str::from_utf8(&buf)?.into()); + } + } + Ok(shader) +} + +pub struct GlGlyphTexture { + pub name: usize, +} + +impl GlGlyphTexture { + pub fn new((width, height): (u32, u32)) -> Self { + let mut name = 0; + unsafe { + // Create a texture for the glyphs + // The texture holds 1 byte per pixel as alpha data + gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); + gl::GenTextures(1, &mut name); + gl::BindTexture(gl::TEXTURE_2D, name); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as _); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as _); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RED as _, + width as _, + height as _, + 0, + gl::RED, + gl::UNSIGNED_BYTE, + ptr::null(), + ); + gl_assert_ok!(); + + Self { name } + } + } + + pub fn clear(&self) { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.name); + gl::ClearTexImage( + self.name, + 0, + gl::RED, + gl::UNSIGNED_BYTE, + [12_u8].as_ptr() as _, + ); + gl_assert_ok!(); + } + } +} + +impl Drop for GlGlyphTexture { + fn drop(&mut self) { + unsafe { + gl::DeleteTextures(1, &self.name); + } + } +} + +pub struct GlTextPipe { + shaders: [GLuint; 2], + program: GLuint, + vao: GLuint, + vbo: GLuint, + transform_uniform: GLint, + vertex_count: usize, + vertex_buffer_len: usize, +} + +impl GlTextPipe { + pub fn new(window_size: glutin::dpi::PhysicalSize) -> Res { + let (w, h) = (window_size.width as f32, window_size.height as f32); + + let vs = compile_shader(include_str!("shader/text.vs"), gl::VERTEX_SHADER)?; + let fs = compile_shader(include_str!("shader/text.fs"), gl::FRAGMENT_SHADER)?; + let program = link_program(vs, fs)?; + + let mut vao = 0; + let mut vbo = 0; + + let transform_uniform = unsafe { + // Create Vertex Array Object + gl::GenVertexArrays(1, &mut vao); + gl::BindVertexArray(vao); + + // Create a Vertex Buffer Object + gl::GenBuffers(1, &mut vbo); + gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + + // Use shader program + gl::UseProgram(program); + gl::BindFragDataLocation(program, 0, CString::new("out_color")?.as_ptr()); + + // Specify the layout of the vertex data + let uniform = gl::GetUniformLocation(program, CString::new("transform")?.as_ptr()); + if uniform < 0 { + return Err(format!("GetUniformLocation(\"transform\") -> {}", uniform).into()); + } + let transform = ortho(0.0, w, 0.0, h, 1.0, -1.0); + gl::UniformMatrix4fv(uniform, 1, 0, transform.as_ptr()); + + let mut offset = 0; + for (v_field, float_count) in &[ + ("left_top", 3), + ("right_bottom", 2), + ("tex_left_top", 2), + ("tex_right_bottom", 2), + ("color", 4), + ] { + let attr = gl::GetAttribLocation(program, CString::new(*v_field)?.as_ptr()); + if attr < 0 { + return Err(format!("{} GetAttribLocation -> {}", v_field, attr).into()); + } + gl::VertexAttribPointer( + attr as _, + *float_count, + gl::FLOAT, + gl::FALSE as _, + mem::size_of::() as _, + offset as _, + ); + gl::EnableVertexAttribArray(attr as _); + gl::VertexAttribDivisor(attr as _, 1); + + offset += float_count * 4; + } + + // Enabled alpha blending + gl::Enable(gl::BLEND); + gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); + // Use srgb for consistency with other examples + gl::Enable(gl::FRAMEBUFFER_SRGB); + gl::ClearColor(0.02, 0.02, 0.02, 1.0); + gl_assert_ok!(); + + uniform + }; + + Ok(Self { + shaders: [vs, fs], + program, + vao, + vbo, + transform_uniform, + vertex_count: 0, + vertex_buffer_len: 0, + }) + } + + pub fn upload_vertices(&mut self, vertices: &[Vertex]) { + // Draw new vertices + self.vertex_count = vertices.len(); + + unsafe { + gl::BindVertexArray(self.vao); + gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo); + if self.vertex_buffer_len < self.vertex_count { + gl::BufferData( + gl::ARRAY_BUFFER, + (self.vertex_count * mem::size_of::()) as GLsizeiptr, + vertices.as_ptr() as _, + gl::DYNAMIC_DRAW, + ); + self.vertex_buffer_len = self.vertex_count; + } else { + gl::BufferSubData( + gl::ARRAY_BUFFER, + 0, + (self.vertex_count * mem::size_of::()) as GLsizeiptr, + vertices.as_ptr() as _, + ); + } + gl_assert_ok!(); + } + } + + pub fn update_geometry(&self, window_size: glutin::dpi::PhysicalSize) { + let (w, h) = (window_size.width as f32, window_size.height as f32); + let transform = ortho(0.0, w, 0.0, h, 1.0, -1.0); + + unsafe { + gl::UseProgram(self.program); + gl::UniformMatrix4fv(self.transform_uniform, 1, 0, transform.as_ptr()); + gl_assert_ok!(); + } + } + + pub fn draw(&self) { + unsafe { + gl::UseProgram(self.program); + gl::BindVertexArray(self.vao); + gl::DrawArraysInstanced(gl::TRIANGLE_STRIP, 0, 4, self.vertex_count as _); + gl_assert_ok!(); + } + } +} + +impl Drop for GlTextPipe { + fn drop(&mut self) { + unsafe { + gl::DeleteProgram(self.program); + self.shaders.iter().for_each(|s| gl::DeleteShader(*s)); + gl::DeleteBuffers(1, &self.vbo); + gl::DeleteVertexArrays(1, &self.vao); + } + } +} From 66fd716a8cbde6f2dd713c30a8d4bd8b862bf502 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 29 May 2020 23:23:37 -0400 Subject: [PATCH 14/37] Revert "wip try merging in glyph stuff" This reverts commit d66e16ce7a9b35ff5881b10d5012057a3b2d4708. --- Cargo.lock | 108 ---------------- editor/Cargo.toml | 1 - editor/src/lib.rs | 304 +--------------------------------------------- 3 files changed, 4 insertions(+), 409 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efdb021c24..8605000b24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "ab_glyph" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d4cab67fcb3051906d685028ad5e846bad471a6d468943c6cbcc498a8a33559" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b7e4e8cf778db814365e46839949ca74df4efb10e87ba4913e6ec5967ef0285" - [[package]] name = "adler32" version = "1.0.4" @@ -447,16 +431,6 @@ dependencies = [ "itertools", ] -[[package]] -name = "crossbeam-channel" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" -dependencies = [ - "crossbeam-utils", - "maybe-uninit", -] - [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -790,45 +764,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "glyph_brush" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afd3e2cfd503a5218dd56172a8bf7c8655a4a7cf745737c606a6edfeea1b343f" -dependencies = [ - "glyph_brush_draw_cache", - "glyph_brush_layout", - "log", - "ordered-float", - "rustc-hash", - "twox-hash", -] - -[[package]] -name = "glyph_brush_draw_cache" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588662439c80d2784e6bb1274e2a6378ffcc74f8006b02e67e905039e200764d" -dependencies = [ - "ab_glyph", - "crossbeam-channel", - "crossbeam-deque", - "linked-hash-map", - "rayon", - "rustc-hash", -] - -[[package]] -name = "glyph_brush_layout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa49abf7dcf7bfe68f42c1c8ab7473505aaba14de84afb8899a0109b6c61717" -dependencies = [ - "ab_glyph", - "approx", - "xi-unicode", -] - [[package]] name = "heck" version = "0.3.1" @@ -1060,12 +995,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "linked-hash-map" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" - [[package]] name = "llvm-sys" version = "100.0.1" @@ -1364,15 +1293,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "owned_ttf_parser" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" -dependencies = [ - "ttf-parser", -] - [[package]] name = "parking_lot" version = "0.10.2" @@ -1910,7 +1830,6 @@ dependencies = [ "gfx-backend-vulkan", "gfx-hal", "glsl-to-spirv", - "glyph_brush", "im", "im-rc", "image", @@ -2201,12 +2120,6 @@ dependencies = [ "roc_types", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc_version" version = "0.2.3" @@ -2537,21 +2450,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "ttf-parser" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52fbe7769f5af5d7d25aea74b9443b64e544a5ffb4d2b2968295ddea934f1a06" - -[[package]] -name = "twox-hash" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56" -dependencies = [ - "rand 0.7.3", -] - [[package]] name = "typed-arena" version = "2.0.1" @@ -2898,12 +2796,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" -[[package]] -name = "xi-unicode" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7395cdb9d0a6219fa0ea77d08c946adf9c1984c72fcd443ace30365f3daadef7" - [[package]] name = "xml-rs" version = "0.8.3" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index b13a558fb1..fdaea372ed 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -54,7 +54,6 @@ gfx-hal = "0.5" glsl-to-spirv = "0.1" bincode = "1.2" serde = { version = "1.0", features = ["derive"] } -glyph_brush = "0.7" [target.'cfg(target_os = "macos")'.dependencies.backend] package = "gfx-backend-metal" diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 8f3c15e2c9..4e6512781a 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -1,5 +1,7 @@ use crate::text_state::handle_text_input; use core::ptr::read; +use gfx_hal::pso::ShaderStageFlags; +use gfx_hal::pso::{Rect, Viewport}; use gfx_hal::{ adapter::{Adapter, PhysicalDevice}, buffer::Usage as BufferUsage, @@ -12,15 +14,13 @@ use gfx_hal::{ pool::{CommandPool, CommandPoolCreateFlags}, pso::{ BlendState, ColorBlendDesc, ColorMask, EntryPoint, Face, GraphicsPipelineDesc, - GraphicsShaderSet, PipelineStage, Primitive, Rasterizer, ShaderStageFlags, Specialization, - Viewport, + GraphicsShaderSet, PipelineStage, Primitive, Rasterizer, Specialization, }, queue::{CommandQueue, QueueFamily, Submission}, window::{Extent2D, PresentMode, PresentationSurface, Surface, SwapchainConfig}, Backend, Instance, MemoryTypeId, }; use glsl_to_spirv::ShaderType; -use glyph_brush::ab_glyph::{point, Rect}; use std::borrow::Borrow; use std::io; use std::marker::PhantomData; @@ -421,63 +421,6 @@ struct PushConstants { scale: [f32; 2], } -pub type Vertex = [f32; 13]; - -#[inline] -pub fn to_vertex( - glyph_brush::GlyphVertex { - mut tex_coords, - pixel_coords, - bounds, - extra, - }: glyph_brush::GlyphVertex, -) -> Vertex { - let gl_bounds = bounds; - - let mut gl_rect = Rect { - min: point(pixel_coords.min.x as f32, pixel_coords.min.y as f32), - max: point(pixel_coords.max.x as f32, pixel_coords.max.y as f32), - }; - - // handle overlapping bounds, modify uv_rect to preserve texture aspect - if gl_rect.max.x > gl_bounds.max.x { - let old_width = gl_rect.width(); - gl_rect.max.x = gl_bounds.max.x; - tex_coords.max.x = tex_coords.min.x + tex_coords.width() * gl_rect.width() / old_width; - } - if gl_rect.min.x < gl_bounds.min.x { - let old_width = gl_rect.width(); - gl_rect.min.x = gl_bounds.min.x; - tex_coords.min.x = tex_coords.max.x - tex_coords.width() * gl_rect.width() / old_width; - } - if gl_rect.max.y > gl_bounds.max.y { - let old_height = gl_rect.height(); - gl_rect.max.y = gl_bounds.max.y; - tex_coords.max.y = tex_coords.min.y + tex_coords.height() * gl_rect.height() / old_height; - } - if gl_rect.min.y < gl_bounds.min.y { - let old_height = gl_rect.height(); - gl_rect.min.y = gl_bounds.min.y; - tex_coords.min.y = tex_coords.max.y - tex_coords.height() * gl_rect.height() / old_height; - } - - [ - gl_rect.min.x, - gl_rect.max.y, - extra.z, - gl_rect.max.x, - gl_rect.min.y, - tex_coords.min.x, - tex_coords.max.y, - tex_coords.max.x, - tex_coords.min.y, - extra.color[0], - extra.color[1], - extra.color[2], - extra.color[3], - ] -} - fn run_event_loop() -> Result<(), &'static str> { // TODO do a better window size const WINDOW_SIZE: [u32; 2] = [512, 512]; @@ -823,7 +766,7 @@ fn run_event_loop() -> Result<(), &'static str> { let viewport = { Viewport { - rect: gfx_hal::pso::Rect { + rect: Rect { x: 0, y: 0, w: surface_extent.width as i16, @@ -924,242 +867,3 @@ unsafe fn push_constant_bytes(push_constants: &T) -> &[u32] { let start_ptr = push_constants as *const T as *const u32; std::slice::from_raw_parts(start_ptr, size_in_u32s) } - -// TEXT - -pub fn compile_shader(src: &str, ty: GLenum) -> Res { - let shader; - unsafe { - shader = gl::CreateShader(ty); - // Attempt to compile the shader - let c_str = CString::new(src.as_bytes())?; - gl::ShaderSource(shader, 1, &c_str.as_ptr(), ptr::null()); - gl::CompileShader(shader); - - // Get the compile status - let mut status = GLint::from(gl::FALSE); - gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status); - - // Fail on error - if status != GLint::from(gl::TRUE) { - let mut len = 0; - gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len); - let mut buf = Vec::with_capacity(len as usize); - buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character - gl::GetShaderInfoLog( - shader, - len, - ptr::null_mut(), - buf.as_mut_ptr() as *mut GLchar, - ); - return Err(str::from_utf8(&buf)?.into()); - } - } - Ok(shader) -} - -pub struct GlGlyphTexture { - pub name: usize, -} - -impl GlGlyphTexture { - pub fn new((width, height): (u32, u32)) -> Self { - let mut name = 0; - unsafe { - // Create a texture for the glyphs - // The texture holds 1 byte per pixel as alpha data - gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); - gl::GenTextures(1, &mut name); - gl::BindTexture(gl::TEXTURE_2D, name); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as _); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as _); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _); - gl::TexImage2D( - gl::TEXTURE_2D, - 0, - gl::RED as _, - width as _, - height as _, - 0, - gl::RED, - gl::UNSIGNED_BYTE, - ptr::null(), - ); - gl_assert_ok!(); - - Self { name } - } - } - - pub fn clear(&self) { - unsafe { - gl::BindTexture(gl::TEXTURE_2D, self.name); - gl::ClearTexImage( - self.name, - 0, - gl::RED, - gl::UNSIGNED_BYTE, - [12_u8].as_ptr() as _, - ); - gl_assert_ok!(); - } - } -} - -impl Drop for GlGlyphTexture { - fn drop(&mut self) { - unsafe { - gl::DeleteTextures(1, &self.name); - } - } -} - -pub struct GlTextPipe { - shaders: [GLuint; 2], - program: GLuint, - vao: GLuint, - vbo: GLuint, - transform_uniform: GLint, - vertex_count: usize, - vertex_buffer_len: usize, -} - -impl GlTextPipe { - pub fn new(window_size: glutin::dpi::PhysicalSize) -> Res { - let (w, h) = (window_size.width as f32, window_size.height as f32); - - let vs = compile_shader(include_str!("shader/text.vs"), gl::VERTEX_SHADER)?; - let fs = compile_shader(include_str!("shader/text.fs"), gl::FRAGMENT_SHADER)?; - let program = link_program(vs, fs)?; - - let mut vao = 0; - let mut vbo = 0; - - let transform_uniform = unsafe { - // Create Vertex Array Object - gl::GenVertexArrays(1, &mut vao); - gl::BindVertexArray(vao); - - // Create a Vertex Buffer Object - gl::GenBuffers(1, &mut vbo); - gl::BindBuffer(gl::ARRAY_BUFFER, vbo); - - // Use shader program - gl::UseProgram(program); - gl::BindFragDataLocation(program, 0, CString::new("out_color")?.as_ptr()); - - // Specify the layout of the vertex data - let uniform = gl::GetUniformLocation(program, CString::new("transform")?.as_ptr()); - if uniform < 0 { - return Err(format!("GetUniformLocation(\"transform\") -> {}", uniform).into()); - } - let transform = ortho(0.0, w, 0.0, h, 1.0, -1.0); - gl::UniformMatrix4fv(uniform, 1, 0, transform.as_ptr()); - - let mut offset = 0; - for (v_field, float_count) in &[ - ("left_top", 3), - ("right_bottom", 2), - ("tex_left_top", 2), - ("tex_right_bottom", 2), - ("color", 4), - ] { - let attr = gl::GetAttribLocation(program, CString::new(*v_field)?.as_ptr()); - if attr < 0 { - return Err(format!("{} GetAttribLocation -> {}", v_field, attr).into()); - } - gl::VertexAttribPointer( - attr as _, - *float_count, - gl::FLOAT, - gl::FALSE as _, - mem::size_of::() as _, - offset as _, - ); - gl::EnableVertexAttribArray(attr as _); - gl::VertexAttribDivisor(attr as _, 1); - - offset += float_count * 4; - } - - // Enabled alpha blending - gl::Enable(gl::BLEND); - gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); - // Use srgb for consistency with other examples - gl::Enable(gl::FRAMEBUFFER_SRGB); - gl::ClearColor(0.02, 0.02, 0.02, 1.0); - gl_assert_ok!(); - - uniform - }; - - Ok(Self { - shaders: [vs, fs], - program, - vao, - vbo, - transform_uniform, - vertex_count: 0, - vertex_buffer_len: 0, - }) - } - - pub fn upload_vertices(&mut self, vertices: &[Vertex]) { - // Draw new vertices - self.vertex_count = vertices.len(); - - unsafe { - gl::BindVertexArray(self.vao); - gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo); - if self.vertex_buffer_len < self.vertex_count { - gl::BufferData( - gl::ARRAY_BUFFER, - (self.vertex_count * mem::size_of::()) as GLsizeiptr, - vertices.as_ptr() as _, - gl::DYNAMIC_DRAW, - ); - self.vertex_buffer_len = self.vertex_count; - } else { - gl::BufferSubData( - gl::ARRAY_BUFFER, - 0, - (self.vertex_count * mem::size_of::()) as GLsizeiptr, - vertices.as_ptr() as _, - ); - } - gl_assert_ok!(); - } - } - - pub fn update_geometry(&self, window_size: glutin::dpi::PhysicalSize) { - let (w, h) = (window_size.width as f32, window_size.height as f32); - let transform = ortho(0.0, w, 0.0, h, 1.0, -1.0); - - unsafe { - gl::UseProgram(self.program); - gl::UniformMatrix4fv(self.transform_uniform, 1, 0, transform.as_ptr()); - gl_assert_ok!(); - } - } - - pub fn draw(&self) { - unsafe { - gl::UseProgram(self.program); - gl::BindVertexArray(self.vao); - gl::DrawArraysInstanced(gl::TRIANGLE_STRIP, 0, 4, self.vertex_count as _); - gl_assert_ok!(); - } - } -} - -impl Drop for GlTextPipe { - fn drop(&mut self) { - unsafe { - gl::DeleteProgram(self.program); - self.shaders.iter().for_each(|s| gl::DeleteShader(*s)); - gl::DeleteBuffers(1, &self.vbo); - gl::DeleteVertexArrays(1, &self.vao); - } - } -} From f10e8172263c747a2e75b1b7479a9f8bbb8159a6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 30 May 2020 01:08:17 -0400 Subject: [PATCH 15/37] Switch to wgpu for rendering --- Cargo.lock | 676 +++++++++++++++----- editor/Cargo.toml | 12 +- editor/Inconsolata-Regular.ttf | Bin 0 -> 92124 bytes editor/src/lib.rs | 1091 ++++++++------------------------ 4 files changed, 779 insertions(+), 1000 deletions(-) create mode 100644 editor/Inconsolata-Regular.ttf diff --git a/Cargo.lock b/Cargo.lock index 8605000b24..15df4eb3ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,10 +1,20 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "adler32" -version = "1.0.4" +name = "ab_glyph" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" +checksum = "7d4cab67fcb3051906d685028ad5e846bad471a6d468943c6cbcc498a8a33559" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7e4e8cf778db814365e46839949ca74df4efb10e87ba4913e6ec5967ef0285" [[package]] name = "aho-corasick" @@ -80,6 +90,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "atom" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c86699c3f02778ec07158376991c8f783dd1f2f95c579ffaf0738dc984b2fe2" + [[package]] name = "atty" version = "0.2.14" @@ -103,16 +119,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -[[package]] -name = "bincode" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" -dependencies = [ - "byteorder", - "serde", -] - [[package]] name = "bitflags" version = "1.2.1" @@ -168,12 +174,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" -[[package]] -name = "bytemuck" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431" - [[package]] name = "byteorder" version = "1.3.4" @@ -306,12 +306,6 @@ dependencies = [ "objc", ] -[[package]] -name = "color_quant" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" - [[package]] name = "copyless" version = "0.1.4" @@ -387,15 +381,6 @@ dependencies = [ "objc", ] -[[package]] -name = "crc32fast" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" -dependencies = [ - "cfg-if", -] - [[package]] name = "criterion" version = "0.3.1" @@ -431,6 +416,16 @@ dependencies = [ "itertools", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" +dependencies = [ + "crossbeam-utils", + "maybe-uninit", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -511,16 +506,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "deflate" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7" -dependencies = [ - "adler32", - "byteorder", -] - [[package]] name = "difference" version = "2.0.0" @@ -579,6 +564,19 @@ dependencies = [ "regex", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -628,6 +626,101 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "futures" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" + +[[package]] +name = "futures-executor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" + +[[package]] +name = "futures-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" +dependencies = [ + "proc-macro-hack", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", +] + +[[package]] +name = "futures-sink" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" + +[[package]] +name = "futures-task" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -665,7 +758,38 @@ checksum = "3b46e6f0031330a0be08d17820f2dcaaa91cb36710a97a9500cb4f1c36e785c8" dependencies = [ "fxhash", "gfx-hal", - "spirv_cross", + "spirv_cross 0.18.0", +] + +[[package]] +name = "gfx-auxil" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bdbf8e8d6883c70e5a0d7379ad8ab3ac95127a3761306b36122d8f1c177a8e" +dependencies = [ + "fxhash", + "gfx-hal", + "spirv_cross 0.20.0", +] + +[[package]] +name = "gfx-backend-dx11" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92de0ddc0fde1a89b2a0e92dcc6bbb554bd34af0135e53a28d5ef064611094a4" +dependencies = [ + "bitflags", + "gfx-auxil 0.4.0", + "gfx-hal", + "libloading", + "log", + "parking_lot", + "range-alloc", + "raw-window-handle", + "smallvec", + "spirv_cross 0.20.0", + "winapi 0.3.8", + "wio", ] [[package]] @@ -676,16 +800,26 @@ checksum = "6facbfcdbb383b3cb7ea0709932ad1273e600a31a242255e80597297ce803dca" dependencies = [ "bitflags", "d3d12", - "gfx-auxil", + "gfx-auxil 0.3.0", "gfx-hal", "log", "range-alloc", "raw-window-handle", "smallvec", - "spirv_cross", + "spirv_cross 0.18.0", "winapi 0.3.8", ] +[[package]] +name = "gfx-backend-empty" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67bd2d7bc022b257ddbdabc5fa3b10c29c292372c3409f2b6a6e3f4e11cdb85" +dependencies = [ + "gfx-hal", + "raw-window-handle", +] + [[package]] name = "gfx-backend-metal" version = "0.5.1" @@ -699,7 +833,7 @@ dependencies = [ "copyless", "core-graphics 0.19.0", "foreign-types", - "gfx-auxil", + "gfx-auxil 0.3.0", "gfx-hal", "lazy_static", "log", @@ -709,7 +843,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "smallvec", - "spirv_cross", + "spirv_cross 0.18.0", "storage-map", ] @@ -733,6 +867,17 @@ dependencies = [ "x11", ] +[[package]] +name = "gfx-descriptor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf35f5d66d1bc56e63e68d7528441453f25992bd954b84309d23c659df2c5da" +dependencies = [ + "fxhash", + "gfx-hal", + "log", +] + [[package]] name = "gfx-hal" version = "0.5.0" @@ -744,13 +889,16 @@ dependencies = [ ] [[package]] -name = "gif" -version = "0.10.3" +name = "gfx-memory" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" +checksum = "c2eed6cda674d9cd4d92229102dbd544292124533d236904f987e9afab456137" dependencies = [ - "color_quant", - "lzw", + "fxhash", + "gfx-hal", + "hibitset", + "log", + "slab", ] [[package]] @@ -764,6 +912,45 @@ dependencies = [ "tempfile", ] +[[package]] +name = "glyph_brush" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afd3e2cfd503a5218dd56172a8bf7c8655a4a7cf745737c606a6edfeea1b343f" +dependencies = [ + "glyph_brush_draw_cache", + "glyph_brush_layout", + "log", + "ordered-float", + "rustc-hash", + "twox-hash", +] + +[[package]] +name = "glyph_brush_draw_cache" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588662439c80d2784e6bb1274e2a6378ffcc74f8006b02e67e905039e200764d" +dependencies = [ + "ab_glyph", + "crossbeam-channel", + "crossbeam-deque", + "linked-hash-map", + "rayon", + "rustc-hash", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa49abf7dcf7bfe68f42c1c8ab7473505aaba14de84afb8899a0109b6c61717" +dependencies = [ + "ab_glyph", + "approx", + "xi-unicode", +] + [[package]] name = "heck" version = "0.3.1" @@ -782,6 +969,24 @@ dependencies = [ "libc", ] +[[package]] +name = "hibitset" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a1bb8316a44459a7d14253c4d28dd7395cbd23cc04a68c46e851b8e46d64b1" +dependencies = [ + "atom", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "im" version = "14.3.0" @@ -810,24 +1015,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "image" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f4167a8f21fa2bb3f17a652a760acd7572645281c98e3b612a26242c96ee" -dependencies = [ - "bytemuck", - "byteorder", - "gif", - "jpeg-decoder", - "num-iter", - "num-rational", - "num-traits", - "png", - "scoped_threadpool", - "tiff", -] - [[package]] name = "indexmap" version = "1.3.2" @@ -860,15 +1047,6 @@ dependencies = [ "unindent", ] -[[package]] -name = "inflate" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" -dependencies = [ - "adler32", -] - [[package]] name = "inkwell" version = "0.1.0" @@ -929,16 +1107,6 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" -[[package]] -name = "jpeg-decoder" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b47b4c4e017b01abdc5bcc126d2d1002e5a75bbe3ce73f9f4f311a916363704" -dependencies = [ - "byteorder", - "rayon", -] - [[package]] name = "js-sys" version = "0.3.37" @@ -995,6 +1163,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + [[package]] name = "llvm-sys" version = "100.0.1" @@ -1026,12 +1200,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "lzw" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1093,15 +1261,6 @@ dependencies = [ "objc", ] -[[package]] -name = "miniz_oxide" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" -dependencies = [ - "adler32", -] - [[package]] name = "mio" version = "0.6.21" @@ -1202,38 +1361,6 @@ dependencies = [ "void", ] -[[package]] -name = "num-integer" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" -dependencies = [ - "autocfg 1.0.0", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" -dependencies = [ - "autocfg 1.0.0", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" -dependencies = [ - "autocfg 1.0.0", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.11" @@ -1293,6 +1420,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "owned_ttf_parser" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" +dependencies = [ + "ttf-parser", +] + [[package]] name = "parking_lot" version = "0.10.2" @@ -1317,18 +1453,66 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "peek-poke" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93fd6a575ebf1ac2668d08443c97a22872cfb463fd8b7ddd141e9f6be59af2f" +dependencies = [ + "peek-poke-derive", +] + +[[package]] +name = "peek-poke-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb44a25c5bba983be0fc8592dfaf3e6d0935ce8be0c6b15b2a39507af34a926" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", + "synstructure", + "unicode-xid 0.2.0", +] + [[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pin-project" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", +] + [[package]] name = "pin-project-lite" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.17" @@ -1347,18 +1531,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "png" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c68a431ed29933a4eb5709aca9800989758c97759345860fa5db3cfced0b65d" -dependencies = [ - "bitflags", - "crc32fast", - "deflate", - "inflate", -] - [[package]] name = "ppv-lite86" version = "0.2.6" @@ -1407,6 +1579,12 @@ version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +[[package]] +name = "proc-macro-nested" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" + [[package]] name = "proc-macro2" version = "0.4.30" @@ -1425,13 +1603,19 @@ dependencies = [ "unicode-xid 0.2.0", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quickcheck" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c35d9c36a562f37eca96e79f66d5fd56eefbc22560dacc4a864cabd2d277456" dependencies = [ - "env_logger", + "env_logger 0.6.2", "log", "rand 0.6.5", "rand_core 0.4.2", @@ -1823,19 +2007,20 @@ dependencies = [ name = "roc_editor" version = "0.1.0" dependencies = [ - "bincode", "bumpalo", + "env_logger 0.7.1", + "futures", "gfx-backend-dx12", "gfx-backend-metal", "gfx-backend-vulkan", - "gfx-hal", "glsl-to-spirv", + "glyph_brush", "im", "im-rc", - "image", "indoc", "inkwell", "inlinable_string", + "log", "maplit", "pretty_assertions", "quickcheck", @@ -1856,10 +2041,12 @@ dependencies = [ "roc_types", "roc_unify", "roc_uniq", - "serde", "target-lexicon", "tokio", + "wgpu", + "wgpu_glyph", "winit", + "zerocopy", ] [[package]] @@ -2120,6 +2307,12 @@ dependencies = [ "roc_types", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2164,12 +2357,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" - [[package]] name = "scopeguard" version = "1.1.0" @@ -2196,9 +2383,6 @@ name = "serde" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" -dependencies = [ - "serde_derive", -] [[package]] name = "serde_derive" @@ -2305,6 +2489,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "spirv_cross" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33a9478e9c78782dd694d05dee074703a9c4c74b511de742b88a7e8149f1b37" +dependencies = [ + "cc", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "stb_truetype" version = "0.3.1" @@ -2362,6 +2557,18 @@ dependencies = [ "syn 1.0.17", ] +[[package]] +name = "synstructure" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", + "unicode-xid 0.2.0", +] + [[package]] name = "target-lexicon" version = "0.10.0" @@ -2409,17 +2616,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "tiff" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002351e428db1eb1d8656d4ca61947c3519ac3191e1c804d4600cd32093b77ad" -dependencies = [ - "byteorder", - "lzw", - "miniz_oxide", -] - [[package]] name = "tinytemplate" version = "1.0.3" @@ -2450,6 +2646,21 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "ttf-parser" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52fbe7769f5af5d7d25aea74b9443b64e544a5ffb4d2b2968295ddea934f1a06" + +[[package]] +name = "twox-hash" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56" +dependencies = [ + "rand 0.7.3", +] + [[package]] name = "typed-arena" version = "2.0.1" @@ -2677,6 +2888,85 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wgpu" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf715eb8571da470b856ecc67b057221360d9fce16f3e38001b2fb158d04012" +dependencies = [ + "arrayvec", + "parking_lot", + "raw-window-handle", + "smallvec", + "wgpu-core", + "wgpu-native", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b093098e0782b0f46f154fac5c670ba27b7a5bff98dc943422c13852c696a2b0" +dependencies = [ + "arrayvec", + "bitflags", + "copyless", + "fxhash", + "gfx-backend-dx11", + "gfx-backend-dx12", + "gfx-backend-empty", + "gfx-backend-metal", + "gfx-backend-vulkan", + "gfx-descriptor", + "gfx-hal", + "gfx-memory", + "log", + "parking_lot", + "peek-poke", + "smallvec", + "vec_map", + "wgpu-types", +] + +[[package]] +name = "wgpu-native" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a5051a357d071fd69c24671e0ea6d644a83c7418e47eac3511427379007403" +dependencies = [ + "arrayvec", + "lazy_static", + "libc", + "objc", + "parking_lot", + "raw-window-handle", + "wgpu-core", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3474b5ce2ed628e158c2fe4387a469b2ee119604556aa2debd10d830cedc3bc" +dependencies = [ + "bitflags", + "peek-poke", +] + +[[package]] +name = "wgpu_glyph" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe5558ee779dfad0d53d444be128c539cf8a6e7cad2e7ad9df6a6a28ff5a483" +dependencies = [ + "glyph_brush", + "log", + "wgpu", + "zerocopy", +] + [[package]] name = "winapi" version = "0.2.8" @@ -2749,6 +3039,15 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" @@ -2796,8 +3095,35 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" +[[package]] +name = "xi-unicode" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7395cdb9d0a6219fa0ea77d08c946adf9c1984c72fcd443ace30365f3daadef7" + [[package]] name = "xml-rs" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" + +[[package]] +name = "zerocopy" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" +dependencies = [ + "proc-macro2 1.0.10", + "syn 1.0.17", + "synstructure", +] diff --git a/editor/Cargo.toml b/editor/Cargo.toml index fdaea372ed..aa4c3e4279 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -49,11 +49,13 @@ tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" } target-lexicon = "0.10" winit = "0.22" -image = "0.23" -gfx-hal = "0.5" -glsl-to-spirv = "0.1" -bincode = "1.2" -serde = { version = "1.0", features = ["derive"] } +wgpu = "0.5" +glyph_brush = "0.7" +log = "0.4" +zerocopy = "0.3" +env_logger = "0.7" +futures = "0.3" +wgpu_glyph = "0.9" [target.'cfg(target_os = "macos")'.dependencies.backend] package = "gfx-backend-metal" diff --git a/editor/Inconsolata-Regular.ttf b/editor/Inconsolata-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3e547460b79225335bbc935a679a59da6dc99eae GIT binary patch literal 92124 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 literal 0 HcmV?d00001 diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 4e6512781a..1dada3da5d 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -1,32 +1,9 @@ -use crate::text_state::handle_text_input; -use core::ptr::read; -use gfx_hal::pso::ShaderStageFlags; -use gfx_hal::pso::{Rect, Viewport}; -use gfx_hal::{ - adapter::{Adapter, PhysicalDevice}, - buffer::Usage as BufferUsage, - command::{ClearColor, ClearValue, CommandBuffer, CommandBufferFlags, Level, SubpassContents}, - device::Device, - format::{Aspects, ChannelType, Format}, - image::{Extent, Layout, SubresourceRange}, - memory::{Properties, Requirements, Segment}, - pass::{Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp, Subpass, SubpassDesc}, - pool::{CommandPool, CommandPoolCreateFlags}, - pso::{ - BlendState, ColorBlendDesc, ColorMask, EntryPoint, Face, GraphicsPipelineDesc, - GraphicsShaderSet, PipelineStage, Primitive, Rasterizer, Specialization, - }, - queue::{CommandQueue, QueueFamily, Submission}, - window::{Extent2D, PresentMode, PresentationSurface, Surface, SwapchainConfig}, - Backend, Instance, MemoryTypeId, -}; -use glsl_to_spirv::ShaderType; -use std::borrow::Borrow; +use std::error::Error; use std::io; -use std::marker::PhantomData; -use std::mem::{size_of, ManuallyDrop}; use std::path::Path; -use winit::event::ModifiersState; +use wgpu_glyph::{ab_glyph, GlyphBrushBuilder, Section, Text}; +use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; +use winit::event_loop::ControlFlow; pub mod text_state; @@ -40,613 +17,71 @@ pub fn launch(_filepaths: &[&Path]) -> io::Result<()> { Ok(()) } -use winit::{event_loop::EventLoop, window::WindowBuilder}; +fn run_event_loop() -> Result<(), Box> { + env_logger::init(); -struct Resources { - instance: B::Instance, - surface: B::Surface, - device: B::Device, - render_passes: Vec, - pipeline_layouts: Vec, - pipelines: Vec, - command_pool: B::CommandPool, - submission_complete_fence: B::Fence, - rendering_complete_semaphore: B::Semaphore, - vertices: BufferBundle, - indexes: BufferBundle, -} + // Open window and create a surface + let event_loop = winit::event_loop::EventLoop::new(); -struct ResourceHolder(ManuallyDrop>); - -impl Drop for ResourceHolder { - fn drop(&mut self) { - unsafe { - let Resources { - instance, - mut surface, - device, - command_pool, - render_passes, - pipeline_layouts, - pipelines, - indexes, - vertices, - submission_complete_fence, - rendering_complete_semaphore, - } = ManuallyDrop::take(&mut self.0); - - device.destroy_semaphore(rendering_complete_semaphore); - device.destroy_fence(submission_complete_fence); - for pipeline in pipelines { - device.destroy_graphics_pipeline(pipeline); - } - for pipeline_layout in pipeline_layouts { - device.destroy_pipeline_layout(pipeline_layout); - } - for render_pass in render_passes { - device.destroy_render_pass(render_pass); - } - device.destroy_command_pool(command_pool); - surface.unconfigure_swapchain(&device); - instance.destroy_surface(surface); - } - } -} - -pub const VERTEX_SOURCE: &str = "#version 450 -layout (location = 0) in vec2 position; -layout (location = 1) in vec3 color; -layout (location = 2) in vec2 vert_uv; -layout (location = 0) out gl_PerVertex { - vec4 gl_Position; -}; -layout (location = 1) out vec3 frag_color; -layout (location = 2) out vec2 frag_uv; -void main() -{ - gl_Position = vec4(position, 0.0, 1.0); - frag_color = color; - frag_uv = vert_uv; -}"; - -pub const FRAGMENT_SOURCE: &str = "#version 450 -layout (push_constant) uniform PushConsts { - float time; -} push; -layout(set = 0, binding = 0) uniform texture2D tex; -layout(set = 0, binding = 1) uniform sampler samp; -layout (location = 1) in vec3 frag_color; -layout (location = 2) in vec2 frag_uv; -layout (location = 0) out vec4 color; -void main() -{ - float time01 = -0.9 * abs(sin(push.time * 0.7)) + 0.9; - vec4 tex_color = texture(sampler2D(tex, samp), frag_uv); - color = mix(tex_color, vec4(frag_color, 1.0), time01); -}"; - -pub static CREATURE_BYTES: &[u8] = include_bytes!("../creature.png"); - -#[derive(Debug, Clone, Copy)] -pub struct Quad { - pub x: f32, - pub y: f32, - pub w: f32, - pub h: f32, -} -impl Quad { - pub fn vertex_attributes(self) -> [f32; 4 * (2 + 3 + 2)] { - let x = self.x; - let y = self.y; - let w = self.w; - let h = self.h; - #[cfg_attr(rustfmt, rustfmt_skip)] - [ - // X Y R G B U V - x , y+h, 1.0, 0.0, 0.0, /* red */ 0.0, 1.0, /* bottom left */ - x , y , 0.0, 1.0, 0.0, /* green */ 0.0, 0.0, /* top left */ - x+w, y , 0.0, 0.0, 1.0, /* blue */ 1.0, 0.0, /* bottom right */ - x+w, y+h, 1.0, 0.0, 1.0, /* magenta */ 1.0, 1.0, /* top right */ - ] - } -} - -pub struct BufferBundle> { - pub buffer: ManuallyDrop, - pub requirements: Requirements, - pub memory: ManuallyDrop, - pub phantom: PhantomData, -} -impl> BufferBundle { - pub fn new( - adapter: &Adapter, - device: &D, - size: usize, - usage: BufferUsage, - ) -> Result { - unsafe { - let mut buffer = device - .create_buffer(size as u64, usage) - .map_err(|_| "Couldn't create a buffer!")?; - let requirements = device.get_buffer_requirements(&buffer); - let memory_type_id = adapter - .physical_device - .memory_properties() - .memory_types - .iter() - .enumerate() - .find(|&(id, memory_type)| { - requirements.type_mask & (1 << id) != 0 - && memory_type.properties.contains(Properties::CPU_VISIBLE) - }) - .map(|(id, _)| MemoryTypeId(id)) - .ok_or("Couldn't find a memory type to support the buffer!")?; - let memory = device - .allocate_memory(memory_type_id, requirements.size) - .map_err(|_| "Couldn't allocate buffer memory!")?; - device - .bind_buffer_memory(&memory, 0, &mut buffer) - .map_err(|_| "Couldn't bind the buffer memory!")?; - Ok(Self { - buffer: ManuallyDrop::new(buffer), - requirements, - memory: ManuallyDrop::new(memory), - phantom: PhantomData, - }) - } - } - - pub unsafe fn manually_drop(&self, device: &D) { - device.destroy_buffer(ManuallyDrop::into_inner(read(&self.buffer))); - device.free_memory(ManuallyDrop::into_inner(read(&self.memory))); - } -} - -pub struct LoadedImage { - pub image: ManuallyDrop, - pub requirements: Requirements, - pub memory: ManuallyDrop, - pub image_view: ManuallyDrop, - pub sampler: ManuallyDrop, - pub phantom: PhantomData, -} - -impl LoadedImage { - pub fn new( - adapter: &Adapter, - device: &B::Device, - command_pool: &mut B::CommandPool, - command_queue: &mut B::CommandQueue, - img: image::RgbaImage, - ) -> Result { - unsafe { - // 0. First we compute some memory related values. - let pixel_size = size_of::>(); - let row_size = pixel_size * (img.width() as usize); - let limits = adapter.physical_device.limits(); - let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; - let row_pitch = ((row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; - debug_assert!(row_pitch as usize >= row_size); - - // 1. make a staging buffer with enough memory for the image, and a - // transfer_src usage - let required_bytes = row_pitch * img.height() as usize; - let staging_bundle = - BufferBundle::new(&adapter, device, required_bytes, BufferUsage::TRANSFER_SRC)?; - - // 2. use mapping writer to put the image data into that buffer - unsafe { - let mapping = device - .map_memory(&staging_bundle.memory, Segment::ALL) - .unwrap(); - - for y in 0..img.height() as usize { - let row = &(*img)[y * row_size..(y + 1) * row_size]; - let dest_base = y * row_pitch; - - mapping[dest_base..dest_base + row.len()].copy_from_slice(row); - } - - device.unmap_memory(&staging_bundle.memory); - } - - // 3. Make an image with transfer_dst and SAMPLED usage - let mut the_image = device - .create_image( - gfx_hal::image::Kind::D2(img.width(), img.height(), 1, 1), - 1, - Format::Rgba8Srgb, - gfx_hal::image::Tiling::Optimal, - gfx_hal::image::Usage::TRANSFER_DST | gfx_hal::image::Usage::SAMPLED, - gfx_hal::image::ViewCapabilities::empty(), - ) - .map_err(|_| "Couldn't create the image!")?; - - // 4. allocate memory for the image and bind it - let requirements = device.get_image_requirements(&the_image); - let memory_type_id = adapter - .physical_device - .memory_properties() - .memory_types - .iter() - .enumerate() - .find(|&(id, memory_type)| { - // BIG NOTE: THIS IS DEVICE LOCAL NOT CPU VISIBLE - requirements.type_mask & (1 << id) != 0 - && memory_type.properties.contains(Properties::DEVICE_LOCAL) - }) - .map(|(id, _)| MemoryTypeId(id)) - .ok_or("Couldn't find a memory type to support the image!")?; - let memory = device - .allocate_memory(memory_type_id, requirements.size) - .map_err(|_| "Couldn't allocate image memory!")?; - device - .bind_image_memory(&memory, 0, &mut the_image) - .map_err(|_| "Couldn't bind the image memory!")?; - - // 5. create image view and sampler - let image_view = device - .create_image_view( - &the_image, - gfx_hal::image::ViewKind::D2, - Format::Rgba8Srgb, - gfx_hal::format::Swizzle::NO, - SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - ) - .map_err(|_| "Couldn't create the image view!")?; - let sampler = device - .create_sampler(gfx_hal::image::SamplerInfo::new( - gfx_hal::image::Filter::Nearest, - gfx_hal::image::WrapMode::Tile, - )) - .map_err(|_| "Couldn't create the sampler!")?; - - // 6. create a command buffer - let mut cmd_buffer = command_pool.allocate_one(Level::Primary); - cmd_buffer.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); - - // 7. Use a pipeline barrier to transition the image from empty/undefined - // to TRANSFER_WRITE/TransferDstOptimal - let image_barrier = gfx_hal::memory::Barrier::Image { - states: (gfx_hal::image::Access::empty(), Layout::Undefined) - ..( - gfx_hal::image::Access::TRANSFER_WRITE, - Layout::TransferDstOptimal, - ), - target: &the_image, - families: None, - range: SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - }; - cmd_buffer.pipeline_barrier( - PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, - gfx_hal::memory::Dependencies::empty(), - &[image_barrier], - ); - - // 8. perform copy from staging buffer to image - cmd_buffer.copy_buffer_to_image( - &staging_bundle.buffer, - &the_image, - Layout::TransferDstOptimal, - &[gfx_hal::command::BufferImageCopy { - buffer_offset: 0, - buffer_width: (row_pitch / pixel_size) as u32, - buffer_height: img.height(), - image_layers: gfx_hal::image::SubresourceLayers { - aspects: Aspects::COLOR, - level: 0, - layers: 0..1, - }, - image_offset: gfx_hal::image::Offset { x: 0, y: 0, z: 0 }, - image_extent: gfx_hal::image::Extent { - width: img.width(), - height: img.height(), - depth: 1, - }, - }], - ); - - // 9. use pipeline barrier to transition the image to SHADER_READ access/ - // ShaderReadOnlyOptimal layout - let image_barrier = gfx_hal::memory::Barrier::Image { - states: ( - gfx_hal::image::Access::TRANSFER_WRITE, - Layout::TransferDstOptimal, - ) - ..( - gfx_hal::image::Access::SHADER_READ, - Layout::ShaderReadOnlyOptimal, - ), - target: &the_image, - families: None, - range: SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - }; - cmd_buffer.pipeline_barrier( - PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER, - gfx_hal::memory::Dependencies::empty(), - &[image_barrier], - ); - - // 10. Submit the cmd buffer to queue and wait for it - cmd_buffer.finish(); - let upload_fence = device - .create_fence(false) - .map_err(|_| "Couldn't create an upload fence!")?; - command_queue.submit_without_semaphores(Some(&cmd_buffer), Some(&upload_fence)); - device - .wait_for_fence(&upload_fence, core::u64::MAX) - .map_err(|_| "Couldn't wait for the fence!")?; - device.destroy_fence(upload_fence); - - // 11. Destroy the staging bundle and one shot buffer now that we're done - staging_bundle.manually_drop(device); - command_pool.free(Some(cmd_buffer)); - - Ok(Self { - image: ManuallyDrop::new(the_image), - requirements, - memory: ManuallyDrop::new(memory), - image_view: ManuallyDrop::new(image_view), - sampler: ManuallyDrop::new(sampler), - phantom: PhantomData, - }) - } - } - - pub unsafe fn manually_drop(&self, device: &B::Device) { - device.destroy_sampler(ManuallyDrop::into_inner(read(&self.sampler))); - device.destroy_image_view(ManuallyDrop::into_inner(read(&self.image_view))); - device.destroy_image(ManuallyDrop::into_inner(read(&self.image))); - device.free_memory(ManuallyDrop::into_inner(read(&self.memory))); - } -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -struct PushConstants { - color: [f32; 4], - pos: [f32; 2], - scale: [f32; 2], -} - -fn run_event_loop() -> Result<(), &'static str> { - // TODO do a better window size - const WINDOW_SIZE: [u32; 2] = [512, 512]; - - let event_loop = EventLoop::new(); - let (logical_window_size, physical_window_size) = { - use winit::dpi::{LogicalSize, PhysicalSize}; - - let dpi = event_loop.primary_monitor().scale_factor(); - let logical: LogicalSize = WINDOW_SIZE.into(); - let physical: PhysicalSize = logical.to_physical(dpi); - - (logical, physical) - }; - - let mut surface_extent = Extent2D { - width: physical_window_size.width, - height: physical_window_size.height, - }; - - let window = WindowBuilder::new() - .with_title("roc") - .with_inner_size(logical_window_size) + let window = winit::window::WindowBuilder::new() + .with_maximized(true) .build(&event_loop) .unwrap(); - let mut should_configure_swapchain = true; + let surface = wgpu::Surface::create(&window); - let (instance, surface, adapter) = { - let instance = backend::Instance::create("roc_editor", 1).expect("Backend not supported"); - - let surface = unsafe { - instance - .create_surface(&window) - .expect("Failed to create surface for window") - }; - - let adapter = instance.enumerate_adapters().remove(0); - - (instance, surface, adapter) - }; - - let (device, mut queue_group) = { - let queue_family = adapter - .queue_families - .iter() - .find(|family| { - surface.supports_queue_family(family) && family.queue_type().supports_graphics() - }) - .expect("No compatible queue family found"); - - let mut gpu = unsafe { - adapter - .physical_device - .open(&[(queue_family, &[1.0])], gfx_hal::Features::empty()) - .expect("Failed to open device") - }; - - (gpu.device, gpu.queue_groups.pop().unwrap()) - }; - - let (command_pool, mut command_buffer) = unsafe { - let mut command_pool = device - .create_command_pool(queue_group.family, CommandPoolCreateFlags::empty()) - .expect("Out of memory"); - - let command_buffer = command_pool.allocate_one(Level::Primary); - - (command_pool, command_buffer) - }; - - let surface_color_format = { - let supported_formats = surface - .supported_formats(&adapter.physical_device) - .unwrap_or_else(|| vec![]); - - let default_format = *supported_formats.get(0).unwrap_or(&Format::Rgba8Srgb); - - supported_formats - .into_iter() - .find(|format| format.base_format().1 == ChannelType::Srgb) - .unwrap_or(default_format) - }; - - let render_pass = { - let color_attachment = Attachment { - format: Some(surface_color_format), - samples: 1, - ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store), - stencil_ops: AttachmentOps::DONT_CARE, - layouts: Layout::Undefined..Layout::Present, - }; - - let subpass = SubpassDesc { - colors: &[(0, Layout::ColorAttachmentOptimal)], - depth_stencil: None, - inputs: &[], - resolves: &[], - preserves: &[], - }; - - unsafe { - device - .create_render_pass(&[color_attachment], &[subpass], &[]) - .expect("Out of memory") - } - }; - - let pipeline_layout = unsafe { - let push_constant_bytes = std::mem::size_of::() as u32; - - device - .create_pipeline_layout(&[], &[(ShaderStageFlags::VERTEX, 0..push_constant_bytes)]) - .expect("Out of memory") - }; - - let vertex_shader = include_str!("../shaders/triangle.vert"); - let fragment_shader = include_str!("../shaders/triangle.frag"); - - /// Create a pipeline with the given layout and shaders. - unsafe fn make_pipeline( - device: &B::Device, - render_pass: &B::RenderPass, - pipeline_layout: &B::PipelineLayout, - vertex_shader: &str, - fragment_shader: &str, - ) -> Result { - let vertex_shader_module = device - .create_shader_module(&compile_shader(vertex_shader, ShaderType::Vertex)) - .expect("Failed to create vertex shader module"); - - let fragment_shader_module = device - .create_shader_module(&compile_shader(fragment_shader, ShaderType::Fragment)) - .expect("Failed to create fragment shader module"); - - let (vs_entry, fs_entry) = ( - EntryPoint { - entry: "main", - module: &vertex_shader_module, - specialization: Specialization::default(), + // Initialize GPU + let (device, queue) = futures::executor::block_on(async { + let adapter = wgpu::Adapter::request( + &wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), }, - EntryPoint { - entry: "main", - module: &fragment_shader_module, - specialization: Specialization::default(), - }, - ); - - let shader_entries = GraphicsShaderSet { - vertex: vs_entry, - hull: None, - domain: None, - geometry: None, - fragment: Some(fs_entry), - }; - - let mut pipeline_desc = GraphicsPipelineDesc::new( - shader_entries, - Primitive::TriangleList, - Rasterizer { - cull_face: Face::BACK, - ..Rasterizer::FILL - }, - pipeline_layout, - Subpass { - index: 0, - main_pass: render_pass, - }, - ); - - pipeline_desc.blender.targets.push(ColorBlendDesc { - mask: ColorMask::ALL, - blend: Some(BlendState::ALPHA), - }); - - let pipeline = device - .create_graphics_pipeline(&pipeline_desc, None) - .expect("Failed to create graphics pipeline"); - - device.destroy_shader_module(vertex_shader_module); - device.destroy_shader_module(fragment_shader_module); - - Ok(pipeline) - }; - - let pipeline = unsafe { - make_pipeline::( - &device, - &render_pass, - &pipeline_layout, - vertex_shader, - fragment_shader, + wgpu::BackendBit::all(), ) - }?; + .await + .expect("Request adapter"); - let submission_complete_fence = device.create_fence(true).expect("Out of memory"); - let rendering_complete_semaphore = device.create_semaphore().expect("Out of memory"); + adapter + .request_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + limits: wgpu::Limits { max_bind_groups: 1 }, + }) + .await + }); - const F32_XY_RGB_UV_QUAD: usize = size_of::() * (2 + 3 + 2) * 4; - let vertices = BufferBundle::new(&adapter, &device, F32_XY_RGB_UV_QUAD, BufferUsage::VERTEX)?; + // Prepare swap chain + let render_format = wgpu::TextureFormat::Bgra8UnormSrgb; + let mut size = window.inner_size(); - const U16_QUAD_INDICES: usize = size_of::() * 2 * 3; - let indexes = BufferBundle::new(&adapter, &device, U16_QUAD_INDICES, BufferUsage::INDEX)?; - let mut resource_holder: ResourceHolder = - ResourceHolder(ManuallyDrop::new(Resources { - instance, - surface, - device, - command_pool, - render_passes: vec![render_pass], - pipeline_layouts: vec![pipeline_layout], - pipelines: vec![pipeline], - submission_complete_fence, - rendering_complete_semaphore, - vertices, - indexes, - })); - let is_animating = false; + let mut swap_chain = device.create_swap_chain( + &surface, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: render_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Immediate, + }, + ); + + // Prepare glyph_brush + let inconsolata = + ab_glyph::FontArc::try_from_slice(include_bytes!("../Inconsolata-Regular.ttf"))?; + + let mut glyph_brush = GlyphBrushBuilder::using_font(inconsolata).build(&device, render_format); + + let is_animating = true; let mut text_state = String::new(); let mut keyboard_modifiers = ModifiersState::empty(); - event_loop.run(move |event, _, control_flow| { - use winit::event::{Event, WindowEvent}; - use winit::event_loop::ControlFlow; + // Render loop + window.request_redraw(); - // TODO dynamically switch is_animating on/off depending on whether any + event_loop.run(move |event, _, control_flow| { + // TODO dynamically switch this on/off depending on whether any // animations are running. Should conserve CPU usage and battery life! if is_animating { *control_flow = ControlFlow::Poll; @@ -655,215 +90,231 @@ fn run_event_loop() -> Result<(), &'static str> { } match event { - Event::WindowEvent { - event: window_event, + winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::CloseRequested, .. - } => match window_event { - WindowEvent::CloseRequested => { - println!("✈️ Thank you for flying Roc Airlines!"); - *control_flow = ControlFlow::Exit - } - WindowEvent::Resized(dims) => { - surface_extent = Extent2D { - width: dims.width, - height: dims.height, - }; - should_configure_swapchain = true; - } - WindowEvent::KeyboardInput { input, .. } => { - if let Some(virtual_keycode) = input.virtual_keycode { - handle_text_input( - &mut text_state, - input.state, - virtual_keycode, - keyboard_modifiers, - ); - } - } - WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - surface_extent = Extent2D { - width: new_inner_size.width, - height: new_inner_size.height, - }; - should_configure_swapchain = true; - } - WindowEvent::ModifiersChanged(modifiers) => { - keyboard_modifiers = modifiers; - } - _ => (), - }, - Event::MainEventsCleared => window.request_redraw(), - Event::RedrawRequested(_) => { - let res: &mut Resources<_> = &mut resource_holder.0; - let render_pass = &res.render_passes[0]; - let pipeline_layout = &res.pipeline_layouts[0]; - let pipeline = &res.pipelines[0]; + } => *control_flow = winit::event_loop::ControlFlow::Exit, + winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::Resized(new_size), + .. + } => { + size = new_size; - unsafe { - // We refuse to wait more than a second, to avoid hanging. - let render_timeout_ns = 1_000_000_000; - - res.device - .wait_for_fence(&res.submission_complete_fence, render_timeout_ns) - .expect("Out of memory or device lost"); - - res.device - .reset_fence(&res.submission_complete_fence) - .expect("Out of memory"); - - res.command_pool.reset(false); - } - - if should_configure_swapchain { - let caps = res.surface.capabilities(&adapter.physical_device); - - let mut swapchain_config = - SwapchainConfig::from_caps(&caps, surface_color_format, surface_extent) - .with_present_mode(PresentMode::IMMEDIATE); - - // This seems to fix some fullscreen slowdown on macOS. - if caps.image_count.contains(&3) { - swapchain_config.image_count = 3; - } - - surface_extent = swapchain_config.extent; - - unsafe { - res.surface - .configure_swapchain(&res.device, swapchain_config) - .expect("Failed to configure swapchain"); - }; - - should_configure_swapchain = false; - } - - let surface_image = unsafe { - // We refuse to wait more than a second, to avoid hanging. - let acquire_timeout_ns = 1_000_000_000; - - match res.surface.acquire_image(acquire_timeout_ns) { - Ok((image, _)) => image, - Err(_) => { - should_configure_swapchain = true; - return; - } - } - }; - - let framebuffer = unsafe { - res.device - .create_framebuffer( - render_pass, - vec![surface_image.borrow()], - Extent { - width: surface_extent.width, - height: surface_extent.height, - depth: 1, - }, - ) - .unwrap() - }; - - let viewport = { - Viewport { - rect: Rect { - x: 0, - y: 0, - w: surface_extent.width as i16, - h: surface_extent.height as i16, - }, - depth: 0.0..1.0, - } - }; - - let triangles = text_state.chars().enumerate().flat_map(|(index, char)| { - if char == ' ' { - // Don't render spaces - None - } else { - Some(PushConstants { - color: [0.77254902, 0.658823529, 1.0, 0.5], - pos: [0.06 * index as f32, 0.0], - scale: [0.05, 0.05], - }) - } - }); - - unsafe { - command_buffer.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); - - command_buffer.set_viewports(0, &[viewport.clone()]); - command_buffer.set_scissors(0, &[viewport.rect]); - command_buffer.begin_render_pass( - render_pass, - &framebuffer, - viewport.rect, - &[ClearValue { - color: ClearColor { - float32: [0.01, 0.01, 0.01, 1.0], - }, - }], - SubpassContents::Inline, + swap_chain = device.create_swap_chain( + &surface, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: render_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Immediate, + }, + ); + } + winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::KeyboardInput { input, .. }, + .. + } => { + if let Some(virtual_keycode) = input.virtual_keycode { + handle_text_input( + &mut text_state, + input.state, + virtual_keycode, + keyboard_modifiers, ); - command_buffer.bind_graphics_pipeline(pipeline); - - for triangle in triangles { - command_buffer.push_graphics_constants( - pipeline_layout, - ShaderStageFlags::VERTEX, - 0, - push_constant_bytes(&triangle), - ); - - command_buffer.draw(0..3, 0..1); - } - - command_buffer.end_render_pass(); - command_buffer.finish(); - } - - unsafe { - let submission = Submission { - command_buffers: vec![&command_buffer], - wait_semaphores: None, - signal_semaphores: vec![&res.rendering_complete_semaphore], - }; - - queue_group.queues[0].submit(submission, Some(&res.submission_complete_fence)); - let result = queue_group.queues[0].present_surface( - &mut res.surface, - surface_image, - Some(&res.rendering_complete_semaphore), - ); - - should_configure_swapchain |= result.is_err(); - - res.device.destroy_framebuffer(framebuffer); } } - _ => (), + winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::ModifiersChanged(modifiers), + .. + } => { + keyboard_modifiers = modifiers; + } + winit::event::Event::MainEventsCleared => window.request_redraw(), + winit::event::Event::RedrawRequested { .. } => { + // Get a command encoder for the current frame + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Redraw"), + }); + + // Get the next frame + let frame = swap_chain.get_next_texture().expect("Get next frame"); + + // Clear frame + { + let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.4, + g: 0.4, + b: 0.4, + a: 1.0, + }, + }], + depth_stencil_attachment: None, + }); + } + + glyph_brush.queue(Section { + screen_position: (30.0, 30.0), + bounds: (size.width as f32, size.height as f32), + text: vec![Text::new(text_state.as_str()) + .with_color([0.0, 0.0, 0.0, 1.0]) + .with_scale(40.0)], + ..Section::default() + }); + + glyph_brush.queue(Section { + screen_position: (30.0, 90.0), + bounds: (size.width as f32, size.height as f32), + text: vec![Text::new("Hello wgpu_glyph!") + .with_color([1.0, 1.0, 1.0, 1.0]) + .with_scale(40.0)], + ..Section::default() + }); + + // Draw the text! + glyph_brush + .draw_queued(&device, &mut encoder, &frame.view, size.width, size.height) + .expect("Draw queued"); + + queue.submit(&[encoder.finish()]); + } + _ => { + *control_flow = winit::event_loop::ControlFlow::Wait; + } } - }); + }) } -/// Compile some GLSL shader source to SPIR-V. -/// TODO do this at build time - possibly in CI only -fn compile_shader(glsl: &str, shader_type: ShaderType) -> Vec { - use std::io::{Cursor, Read}; +fn handle_text_input( + text_state: &mut String, + elem_state: ElementState, + virtual_keycode: VirtualKeyCode, + _modifiers: ModifiersState, +) { + use winit::event::VirtualKeyCode::*; - let mut compiled_file = - glsl_to_spirv::compile(glsl, shader_type).expect("Failed to compile shader"); + if let ElementState::Released = elem_state { + return; + } - let mut spirv_bytes = vec![]; - compiled_file.read_to_end(&mut spirv_bytes).unwrap(); - - gfx_hal::pso::read_spirv(Cursor::new(&spirv_bytes)).expect("Invalid SPIR-V") -} - -/// Returns a view of a struct as a slice of `u32`s. -unsafe fn push_constant_bytes(push_constants: &T) -> &[u32] { - let size_in_bytes = std::mem::size_of::(); - let size_in_u32s = size_in_bytes / std::mem::size_of::(); - let start_ptr = push_constants as *const T as *const u32; - std::slice::from_raw_parts(start_ptr, size_in_u32s) + match virtual_keycode { + Key1 | Numpad1 => text_state.push_str("1"), + Key2 | Numpad2 => text_state.push_str("2"), + Key3 | Numpad3 => text_state.push_str("3"), + Key4 | Numpad4 => text_state.push_str("4"), + Key5 | Numpad5 => text_state.push_str("5"), + Key6 | Numpad6 => text_state.push_str("6"), + Key7 | Numpad7 => text_state.push_str("7"), + Key8 | Numpad8 => text_state.push_str("8"), + Key9 | Numpad9 => text_state.push_str("9"), + Key0 | Numpad0 => text_state.push_str("0"), + A => text_state.push_str("a"), + B => text_state.push_str("b"), + C => text_state.push_str("c"), + D => text_state.push_str("d"), + E => text_state.push_str("e"), + F => text_state.push_str("f"), + G => text_state.push_str("g"), + H => text_state.push_str("h"), + I => text_state.push_str("i"), + J => text_state.push_str("j"), + K => text_state.push_str("k"), + L => text_state.push_str("l"), + M => text_state.push_str("m"), + N => text_state.push_str("n"), + O => text_state.push_str("o"), + P => text_state.push_str("p"), + Q => text_state.push_str("q"), + R => text_state.push_str("r"), + S => text_state.push_str("s"), + T => text_state.push_str("t"), + U => text_state.push_str("u"), + V => text_state.push_str("v"), + W => text_state.push_str("w"), + X => text_state.push_str("x"), + Y => text_state.push_str("y"), + Z => text_state.push_str("z"), + Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15 + | F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause + | Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose + | Caret | Numlock | AbntC1 | AbntC2 | Ax | Calculator | Capital | Convert | Kana + | Kanji | LAlt | LBracket | LControl | LShift | LWin | Mail | MediaSelect | PlayPause + | Power | PrevTrack | MediaStop | Mute | MyComputer | NavigateForward + | NavigateBackward | NextTrack | NoConvert | OEM102 | RAlt | Sysrq | RBracket + | RControl | RShift | RWin | Sleep | Stop | Unlabeled | VolumeDown | VolumeUp | Wake + | WebBack | WebFavorites | WebForward | WebHome | WebRefresh | WebSearch | Apps | Tab + | WebStop => { + // TODO handle + dbg!(virtual_keycode); + } + Back => { + text_state.pop(); + } + Return | NumpadEnter => { + text_state.push_str("\n"); + } + Space => { + text_state.push_str(" "); + } + Comma | NumpadComma => { + text_state.push_str(","); + } + Add => { + text_state.push_str("+"); + } + Apostrophe => { + text_state.push_str("'"); + } + At => { + text_state.push_str("@"); + } + Backslash => { + text_state.push_str("\\"); + } + Colon => { + text_state.push_str(":"); + } + Period | Decimal => { + text_state.push_str("."); + } + Equals | NumpadEquals => { + text_state.push_str("="); + } + Grave => { + text_state.push_str("`"); + } + Minus | Subtract => { + text_state.push_str("-"); + } + Multiply => { + text_state.push_str("*"); + } + Semicolon => { + text_state.push_str(";"); + } + Slash | Divide => { + text_state.push_str("/"); + } + Underline => { + text_state.push_str("_"); + } + Yen => { + text_state.push_str("¥"); + } + Copy => { + todo!("copy"); + } + Paste => { + todo!("paste"); + } + Cut => { + todo!("cut"); + } + } } From c734c7779a9920b3e5c930626e5e1f8208d06fa4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 30 May 2020 01:31:20 -0400 Subject: [PATCH 16/37] Tweak editor colors --- editor/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 1dada3da5d..898e45dd62 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -149,9 +149,9 @@ fn run_event_loop() -> Result<(), Box> { load_op: wgpu::LoadOp::Clear, store_op: wgpu::StoreOp::Store, clear_color: wgpu::Color { - r: 0.4, - g: 0.4, - b: 0.4, + r: 0.007, + g: 0.007, + b: 0.007, a: 1.0, }, }], @@ -162,8 +162,8 @@ fn run_event_loop() -> Result<(), Box> { glyph_brush.queue(Section { screen_position: (30.0, 30.0), bounds: (size.width as f32, size.height as f32), - text: vec![Text::new(text_state.as_str()) - .with_color([0.0, 0.0, 0.0, 1.0]) + text: vec![Text::new("Enter some text:") + .with_color([0.4666, 0.2, 1.0, 1.0]) .with_scale(40.0)], ..Section::default() }); @@ -171,7 +171,7 @@ fn run_event_loop() -> Result<(), Box> { glyph_brush.queue(Section { screen_position: (30.0, 90.0), bounds: (size.width as f32, size.height as f32), - text: vec![Text::new("Hello wgpu_glyph!") + text: vec![Text::new(text_state.as_str()) .with_color([1.0, 1.0, 1.0, 1.0]) .with_scale(40.0)], ..Section::default() From fe218a5966975172f7536fbfeba753cf15e1e6da Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 30 May 2020 01:42:46 -0400 Subject: [PATCH 17/37] Handle text input from keyboards better --- editor/src/lib.rs | 136 ++++++++-------------------------------------- 1 file changed, 24 insertions(+), 112 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 898e45dd62..1e8f8b4620 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -111,17 +111,32 @@ fn run_event_loop() -> Result<(), Box> { }, ); } + winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::ReceivedCharacter(ch), + .. + } => { + match ch { + '\u{8}' => { + // Backspace deletes a character. + text_state.pop(); + } + '\u{e000}'..='\u{f8ff}' + | '\u{f0000}'..='\u{ffffd}' + | '\u{100000}'..='\u{10fffd}' => { + // These are private use characters; ignore them. + // See http://www.unicode.org/faq/private_use.html + } + _ => { + text_state.push(ch); + } + } + } winit::event::Event::WindowEvent { event: winit::event::WindowEvent::KeyboardInput { input, .. }, .. } => { if let Some(virtual_keycode) = input.virtual_keycode { - handle_text_input( - &mut text_state, - input.state, - virtual_keycode, - keyboard_modifiers, - ); + handle_keydown(input.state, virtual_keycode, keyboard_modifiers); } } winit::event::Event::WindowEvent { @@ -171,7 +186,7 @@ fn run_event_loop() -> Result<(), Box> { glyph_brush.queue(Section { screen_position: (30.0, 90.0), bounds: (size.width as f32, size.height as f32), - text: vec![Text::new(text_state.as_str()) + text: vec![Text::new(format!("{}|", text_state).as_str()) .with_color([1.0, 1.0, 1.0, 1.0]) .with_scale(40.0)], ..Section::default() @@ -191,8 +206,7 @@ fn run_event_loop() -> Result<(), Box> { }) } -fn handle_text_input( - text_state: &mut String, +fn handle_keydown( elem_state: ElementState, virtual_keycode: VirtualKeyCode, _modifiers: ModifiersState, @@ -204,109 +218,6 @@ fn handle_text_input( } match virtual_keycode { - Key1 | Numpad1 => text_state.push_str("1"), - Key2 | Numpad2 => text_state.push_str("2"), - Key3 | Numpad3 => text_state.push_str("3"), - Key4 | Numpad4 => text_state.push_str("4"), - Key5 | Numpad5 => text_state.push_str("5"), - Key6 | Numpad6 => text_state.push_str("6"), - Key7 | Numpad7 => text_state.push_str("7"), - Key8 | Numpad8 => text_state.push_str("8"), - Key9 | Numpad9 => text_state.push_str("9"), - Key0 | Numpad0 => text_state.push_str("0"), - A => text_state.push_str("a"), - B => text_state.push_str("b"), - C => text_state.push_str("c"), - D => text_state.push_str("d"), - E => text_state.push_str("e"), - F => text_state.push_str("f"), - G => text_state.push_str("g"), - H => text_state.push_str("h"), - I => text_state.push_str("i"), - J => text_state.push_str("j"), - K => text_state.push_str("k"), - L => text_state.push_str("l"), - M => text_state.push_str("m"), - N => text_state.push_str("n"), - O => text_state.push_str("o"), - P => text_state.push_str("p"), - Q => text_state.push_str("q"), - R => text_state.push_str("r"), - S => text_state.push_str("s"), - T => text_state.push_str("t"), - U => text_state.push_str("u"), - V => text_state.push_str("v"), - W => text_state.push_str("w"), - X => text_state.push_str("x"), - Y => text_state.push_str("y"), - Z => text_state.push_str("z"), - Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15 - | F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause - | Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose - | Caret | Numlock | AbntC1 | AbntC2 | Ax | Calculator | Capital | Convert | Kana - | Kanji | LAlt | LBracket | LControl | LShift | LWin | Mail | MediaSelect | PlayPause - | Power | PrevTrack | MediaStop | Mute | MyComputer | NavigateForward - | NavigateBackward | NextTrack | NoConvert | OEM102 | RAlt | Sysrq | RBracket - | RControl | RShift | RWin | Sleep | Stop | Unlabeled | VolumeDown | VolumeUp | Wake - | WebBack | WebFavorites | WebForward | WebHome | WebRefresh | WebSearch | Apps | Tab - | WebStop => { - // TODO handle - dbg!(virtual_keycode); - } - Back => { - text_state.pop(); - } - Return | NumpadEnter => { - text_state.push_str("\n"); - } - Space => { - text_state.push_str(" "); - } - Comma | NumpadComma => { - text_state.push_str(","); - } - Add => { - text_state.push_str("+"); - } - Apostrophe => { - text_state.push_str("'"); - } - At => { - text_state.push_str("@"); - } - Backslash => { - text_state.push_str("\\"); - } - Colon => { - text_state.push_str(":"); - } - Period | Decimal => { - text_state.push_str("."); - } - Equals | NumpadEquals => { - text_state.push_str("="); - } - Grave => { - text_state.push_str("`"); - } - Minus | Subtract => { - text_state.push_str("-"); - } - Multiply => { - text_state.push_str("*"); - } - Semicolon => { - text_state.push_str(";"); - } - Slash | Divide => { - text_state.push_str("/"); - } - Underline => { - text_state.push_str("_"); - } - Yen => { - text_state.push_str("¥"); - } Copy => { todo!("copy"); } @@ -316,5 +227,6 @@ fn handle_text_input( Cut => { todo!("cut"); } + _ => {} } } From 7b490053e90432bdc2f0e350c77b3b2858f2b73a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 30 May 2020 09:30:16 -0400 Subject: [PATCH 18/37] Fix backspace on macOS --- editor/src/lib.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 1e8f8b4620..f482ef468d 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -117,8 +117,11 @@ fn run_event_loop() -> Result<(), Box> { } => { match ch { '\u{8}' => { - // Backspace deletes a character. - text_state.pop(); + // In Linux, we get one of these when you press + // backspace, but in macOS we don't. In both, we + // get a Back keydown event. Therefore, we use the + // Back keydown event and ignore this, resulting + // in a system that works in both Linux and macOS. } '\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' @@ -136,7 +139,7 @@ fn run_event_loop() -> Result<(), Box> { .. } => { if let Some(virtual_keycode) = input.virtual_keycode { - handle_keydown(input.state, virtual_keycode, keyboard_modifiers); + handle_keydown(&mut text_state, input.state, virtual_keycode, keyboard_modifiers); } } winit::event::Event::WindowEvent { @@ -207,6 +210,7 @@ fn run_event_loop() -> Result<(), Box> { } fn handle_keydown( + text_state: &mut String, elem_state: ElementState, virtual_keycode: VirtualKeyCode, _modifiers: ModifiersState, @@ -218,6 +222,12 @@ fn handle_keydown( } match virtual_keycode { + Back => { + // Backspace deletes a character. + // In Linux, we get a Unicode character for backspace events + // (which is handled elsewhere), but on macOS we only get one of these. + text_state.pop(); + } Copy => { todo!("copy"); } From 3323cfdd049e93d403bc9baad5b60bb2d87d4fb3 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 30 May 2020 09:43:11 -0400 Subject: [PATCH 19/37] Drop some obsolete dependencies --- Cargo.lock | 82 ----------------------------------------------- editor/Cargo.toml | 16 --------- 2 files changed, 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15df4eb3ba..048f436508 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,12 +69,6 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - [[package]] name = "arrayvec" version = "0.5.1" @@ -140,16 +134,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" -[[package]] -name = "block-buffer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" -dependencies = [ - "arrayref", - "byte-tools", -] - [[package]] name = "bstr" version = "0.2.12" @@ -168,12 +152,6 @@ version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" -[[package]] -name = "byte-tools" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" - [[package]] name = "byteorder" version = "1.3.4" @@ -267,15 +245,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "cmake" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fb25b677f8bf1eb325017cb6bb8452f87969db0fedb4f757b297bee78a7c62" -dependencies = [ - "cc", -] - [[package]] name = "cocoa" version = "0.19.1" @@ -512,15 +481,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" -[[package]] -name = "digest" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" -dependencies = [ - "generic-array", -] - [[package]] name = "dispatch" version = "0.2.0" @@ -577,12 +537,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fnv" version = "1.0.6" @@ -730,15 +684,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "generic-array" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" -dependencies = [ - "typenum", -] - [[package]] name = "getrandom" version = "0.1.14" @@ -901,17 +846,6 @@ dependencies = [ "slab", ] -[[package]] -name = "glsl-to-spirv" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28caebc98746d507603a2d3df66dcbe04e41d4febad0320f3eec1ef72b6bbef1" -dependencies = [ - "cmake", - "sha2", - "tempfile", -] - [[package]] name = "glyph_brush" version = "0.7.0" @@ -2010,10 +1944,6 @@ dependencies = [ "bumpalo", "env_logger 0.7.1", "futures", - "gfx-backend-dx12", - "gfx-backend-metal", - "gfx-backend-vulkan", - "glsl-to-spirv", "glyph_brush", "im", "im-rc", @@ -2406,18 +2336,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" -dependencies = [ - "block-buffer", - "byte-tools", - "digest", - "fake-simd", -] - [[package]] name = "signal-hook-registry" version = "1.2.0" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index aa4c3e4279..ddc22675b7 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -57,22 +57,6 @@ env_logger = "0.7" futures = "0.3" wgpu_glyph = "0.9" -[target.'cfg(target_os = "macos")'.dependencies.backend] -package = "gfx-backend-metal" -version = "0.5" - -[target.'cfg(windows)'.dependencies.backend] -package = "gfx-backend-dx12" -version = "0.5" - -[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies.backend] -package = "gfx-backend-vulkan" -features = ["x11"] -version = "0.5" - -[build-dependencies] -glsl-to-spirv = "0.1" - [dev-dependencies] pretty_assertions = "0.5.1" maplit = "1.0.1" From 81f140b7449318456f486509e5c8fd11784a80a9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 30 May 2020 09:43:27 -0400 Subject: [PATCH 20/37] Don't start maximized --- editor/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index f482ef468d..17c9367c00 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -24,7 +24,6 @@ fn run_event_loop() -> Result<(), Box> { let event_loop = winit::event_loop::EventLoop::new(); let window = winit::window::WindowBuilder::new() - .with_maximized(true) .build(&event_loop) .unwrap(); From 89f27cd5c6c9848449546371365a70d4a17fc1f1 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 30 May 2020 09:43:44 -0400 Subject: [PATCH 21/37] Try tweaking BackendBit to fix Linux Folkert was getting a "Request adapter" panic. Maybe tweaking these settings will fix it? --- editor/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 17c9367c00..911606602d 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -33,10 +33,10 @@ fn run_event_loop() -> Result<(), Box> { let (device, queue) = futures::executor::block_on(async { let adapter = wgpu::Adapter::request( &wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, + power_preference: wgpu::PowerPreference::Default, compatible_surface: Some(&surface), }, - wgpu::BackendBit::all(), + wgpu::BackendBit::PRIMARY, ) .await .expect("Request adapter"); From 0ef7ec8d446378ae617516d134c3c90ea3c8df61 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 30 May 2020 11:39:26 -0400 Subject: [PATCH 22/37] Go back to default power preference and BackendBit --- editor/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 911606602d..17c9367c00 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -33,10 +33,10 @@ fn run_event_loop() -> Result<(), Box> { let (device, queue) = futures::executor::block_on(async { let adapter = wgpu::Adapter::request( &wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::Default, + power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), }, - wgpu::BackendBit::PRIMARY, + wgpu::BackendBit::all(), ) .await .expect("Request adapter"); From 553b46b9c7c07bfd255752609ddf259e73786914 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 31 May 2020 16:48:26 -0400 Subject: [PATCH 23/37] cargo fmt --- editor/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 17c9367c00..fd3fa95305 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -138,7 +138,12 @@ fn run_event_loop() -> Result<(), Box> { .. } => { if let Some(virtual_keycode) = input.virtual_keycode { - handle_keydown(&mut text_state, input.state, virtual_keycode, keyboard_modifiers); + handle_keydown( + &mut text_state, + input.state, + virtual_keycode, + keyboard_modifiers, + ); } } winit::event::Event::WindowEvent { From 68a3550e7e0974924cc5efd3131d625fe2724ca5 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 31 May 2020 16:47:55 -0400 Subject: [PATCH 24/37] Add beginnings of editor/ast --- editor/src/ast.rs | 490 ++++++++++++++++++++++++++++++++++++++++++++++ editor/src/lib.rs | 1 + 2 files changed, 491 insertions(+) create mode 100644 editor/src/ast.rs diff --git a/editor/src/ast.rs b/editor/src/ast.rs new file mode 100644 index 0000000000..c803d11690 --- /dev/null +++ b/editor/src/ast.rs @@ -0,0 +1,490 @@ +use roc_types::subs::Variable; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Problem { + RanOutOfNodeIds, +} + +pub type Res = Result; + +/// The index into a decl's array of nodes. +/// This is a u32 index because no decl is allowed to hold more than 2^32 entries, +/// and it saves space on 64-bit systems compared to a pointer. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct NodeId(u32); + +impl NodeId { + pub const NONE: NodeId = NodeId(std::u32::MAX); + + pub fn as_index(self) -> usize { + self.0 as usize + } +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct Nodes { + nodes: Vec, +} + +impl Nodes { + pub fn push_expr(&mut self, parent_id: NodeId, expr: Expr) -> Res { + let node_id = self.push_node(Node { + parent_id, + content: NodeContent::Expr(expr), + })?; + + self.set_child(parent_id, node_id); + + Ok(node_id) + } + + fn set_child(&mut self, parent_id: NodeId, _child_id: NodeId) { + match self.nodes.get_mut(parent_id.as_index()) { + Some(parent) => match &mut parent.content { + NodeContent::Expr(Expr::Int { .. }) | NodeContent::Expr(Expr::Float { .. }) => { + // This node has no children. No further action needed! + } + NodeContent::Caret { .. } => { + // This node has no children. No further action needed! + } + other => { + todo!("handle set_child for {:?}", other); + } + }, + None => { + // The only reason this bounds check should ever fail + // is that we were passed no parent. + debug_assert!(parent_id == NodeId::NONE); + } + } + } + + // fn remove_node(&mut self, target_id: NodeId) { + // debug_assert!( + // target_id != NodeId::NONE, + // "Called remove_node on NodeId::NONE" + // ); + + // { + // let target = self + // .nodes + // .get_mut(target_id.as_index()) + // .unwrap_or_else(|| panic!("Tried to remove nonexistant node {:?}", target_id)); + // let opt_parent = self.nodes.get_mut(target.parent_id.as_index()); + + // match &mut target.content { + // NodeContent::Expr(Expr::Int { .. }) => { + // // This node has no children, so remove it from its parent and move on. + // match opt_parent { + // Some(parent) => parent.remove_child(target_id), + // None => { + // // The only valid reason the node's parent_id might + // // not have been in the nodes vec is that it's NONE. + // debug_assert!(target.parent_id == NodeId::NONE); + // } + // } + // } + + // NodeContent::Caret { child_id, offset } => { + // match opt_parent { + // Some(parent) => { + // // Go into the parent and replace this caret with + // // the new child_id + // // parent.replace_child(target_id, child_id) + // } + // None => { + // // The only valid reason the node's parent_id might + // // not have been in the nodes vec is that it's NONE. + // debug_assert!(target.parent_id == NodeId::NONE); + // } + // } + // } + + // NodeContent::Removed => { + // panic!("Tried to remove a node that was already removed."); + // } + // } + // } + // } + + fn push_node(&mut self, node: Node) -> Res { + // TODO check to see if we have any removed nodes, and if so, use those + // instead of pushing to the end. This way, we can avoid needing to defrag. + + // The length *before* we pushed == the index of the node we pushed. + let index = self.nodes.len(); + + self.nodes.push(node); + + if index < std::u32::MAX as usize { + Ok(NodeId(index as u32)) + } else { + // u32::MAX is reserved for NodeId::NONE, so if we hit that on a + // saturating add, we've overflowed. Game over. + Err(Problem::RanOutOfNodeIds) + } + } + + pub fn wrap_with_caret(&mut self, target_id: NodeId, offset: u16) -> Res { + let parent_id = self.get(target_id).parent_id; + let caret_id = { + let content = NodeContent::Caret { + child_id: target_id, + offset, + }; + + self.push_node(Node { content, parent_id })? + }; + + // Now that we have the caret_id, update the old + if parent_id != NodeId::NONE { + self.get_mut(target_id).replace_child(target_id, caret_id); + } + + Ok(caret_id) + } + + pub fn len(&self) -> usize { + self.nodes.len() + } + + pub fn is_empty(&self) -> bool { + self.nodes.is_empty() + } + + pub fn split_at_mut(&mut self, index: NodeId) -> (&mut [Node], &mut [Node]) { + self.nodes.split_at_mut(index.0 as usize) + } + + pub fn get(&self, index: NodeId) -> &Node { + self.nodes + .get(index.0 as usize) + .unwrap_or_else(|| panic!("Unable to find node at index {:?}", index.0)) + } + + pub fn get_mut(&mut self, index: NodeId) -> &mut Node { + self.nodes + .get_mut(index.0 as usize) + .unwrap_or_else(|| panic!("Unable to find node at index {:?}", index.0)) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Node { + parent_id: NodeId, + content: NodeContent, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum NodeContent { + Expr(Expr /* TODO should Node have a Variable? */), + // Pattern(Pattern), + Caret { child_id: NodeId, offset: u16 }, + // SelectionStart { offset: u16, child: NodeId }, + // SelectionEnd { offset: u16, child: NodeId }, + Removed, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Expr { + Int { text: String, var: Variable }, + Float { text: String, var: Variable }, + // Lookup { + // name: String, + // var: Variable, + // }, + // If { + // conditional: NodeId, + // then_node: NodeId, + // else_node: NodeId, + // var: Variable, + // }, + // Then { + // child: NodeId, + // var: Variable, + // }, + // Else { + // child: NodeId, + // var: Variable, + // }, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Pattern { + Identifier { text: String, var: Variable }, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Decl { + Def(Def), +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Def { + Body { pattern: NodeId, expr: NodeId }, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Module { + pub nodes: Nodes, + pub decls: Vec, + // Use a Vec over a Set because it'll always be of small length + pub carets: Vec, + pub selections: Vec<(NodeId, NodeId)>, +} + +impl Node { + /// Returns the number of characters removed, so that + /// the caret can be moved back this many indices. + /// (For example, if you backspace over an emoji, that can move the + /// caret back multiple indices.) + pub fn backspace(&mut self, index: u16) -> u16 { + use Expr::*; + + match &mut self.content { + NodeContent::Expr(Int { text, .. }) | NodeContent::Expr(Float { text, .. }) => { + // TODO remove an entire *grapheme cluster* here, not just a + // single character! This will require using a 3rd-party crate. + let removed = text.remove(index as usize); + + // TODO need to re-index any other carets/selections which + // were also in this node - they need to be moved too! + removed.len_utf8() as u16 + } + other => { + todo!("handle backspace for {:?}", other); + } + } + } + + // fn remove_child(&mut self, id_to_remove: NodeId) { + // match &mut self.content { + // NodeContent::Expr(Expr::Int { .. }) | NodeContent::Expr(Expr::Float { .. }) => { + // // This node has no children, so no action is needed. + // } + + // NodeContent::Caret { child_id, .. } => { + // if *child_id == id_to_remove { + // *child_id = NodeId::NONE; + // } + // } + + // NodeContent::Removed => { + // panic!( + // "Tried to remove a child node ({:?}) from a NodeContent::Removed", + // id_to_remove + // ); + // } + // } + // } + + fn replace_child(&mut self, old_id: NodeId, new_id: NodeId) { + match &mut self.content { + NodeContent::Expr(Expr::Int { .. }) | NodeContent::Expr(Expr::Float { .. }) => { + // This node has no children, so no action is needed. + } + + NodeContent::Caret { child_id, .. } => { + if *child_id == old_id { + *child_id = new_id; + } + } + + NodeContent::Removed => { + panic!( + "Tried to replace child node ID {:?} with {:?} in a NodeContent::Removed", + old_id, new_id + ); + } + } + } +} + +impl Module { + pub fn backspace(&mut self) { + if self.nodes.is_empty() { + return; + } + + for &caret_node_id in self.carets.iter() { + debug_assert!(caret_node_id.as_index() <= self.nodes.len()); + + // Use slices around the caret because we'll need to modify the + // child node. Without these slices we'd need multiple simultaneous + // mutable references to self.nodes, which is never allowed. + let (before_caret, caret_and_after) = self.nodes.split_at_mut(caret_node_id); + dbg!(&caret_node_id, &before_caret, &caret_and_after); + let (caret_only, after_caret) = caret_and_after.split_at_mut(1); + + let caret = caret_only.first_mut().unwrap(); + let parent_id = caret.parent_id; + + match &mut caret.content { + NodeContent::Caret { offset, child_id } => { + let child_id = *child_id; + let offset_index = *offset; + + if offset_index != 0 { + // Get the child node from the appropriate slice + let child_node: &mut Node = { + debug_assert!( + child_id != caret_node_id, + "A caret had itself as its own child: {:?}", + caret_node_id + ); + + if child_id > caret_node_id { + after_caret.get_mut(child_id.as_index() - (before_caret.len() + 1)) + } else { + before_caret.get_mut(child_id.as_index()) + } + } + .unwrap_or_else(|| { + panic!("Could not get child node for caret {:?}", caret_node_id) + }); + + // Mutate the child node to apply the backspace operation. + // + // -1 because index 0 is *before* the first character + // in the string (which we already ruled out using the + // above conditional), and child_node.backspace expects + // to be given the index *after* the char to be removed. + let chars_removed = child_node.backspace(offset_index - 1); + + // Mutate the caret to decrement its offset + *offset = offset_index - chars_removed; + } else { + todo!( + "Backspace when caret offset is 0, into parent: {:?}", + parent_id + ); + } + } + other => { + unreachable!("Caret pointed to a non-caret node: {:?}", other); + } + } + } + } +} + +#[test] +fn single_backspace_1_caret() { + use roc_types::subs::VarStore; + + let var_store = VarStore::default(); + let int_var = var_store.fresh(); + let int_node_id; + let caret_node_id; + let expected = { + let mut nodes = Nodes::default(); + + int_node_id = nodes + .push_expr( + NodeId::NONE, + Expr::Int { + text: "abd".into(), + var: int_var, + }, + ) + .unwrap(); + + caret_node_id = nodes.wrap_with_caret(int_node_id, 2).unwrap(); + + nodes + }; + + let actual = { + let mut nodes = Nodes::default(); + + let actual_node_id = nodes + .push_expr( + NodeId::NONE, + Expr::Int { + text: "abcd".into(), + var: int_var, + }, + ) + .unwrap(); + + assert!(int_node_id == actual_node_id); + + let actual_node_id = nodes.wrap_with_caret(int_node_id, 3).unwrap(); + + assert!(caret_node_id == actual_node_id); + + nodes + }; + + let mut module = Module { + nodes: actual, + decls: Vec::new(), + carets: vec![caret_node_id], + selections: Vec::new(), + }; + + module.backspace(); + + assert_eq!(expected, module.nodes); +} + +#[test] +fn double_backspace_1_caret() { + use roc_types::subs::VarStore; + + let var_store = VarStore::default(); + let int_var = var_store.fresh(); + let int_node_id; + let caret_node_id; + let expected = { + let mut nodes = Nodes::default(); + + int_node_id = nodes + .push_expr( + NodeId::NONE, + Expr::Int { + text: "ad".into(), + var: int_var, + }, + ) + .unwrap(); + + caret_node_id = nodes.wrap_with_caret(int_node_id, 1).unwrap(); + + nodes + }; + + let actual = { + let mut nodes = Nodes::default(); + + let actual_node_id = nodes + .push_expr( + NodeId::NONE, + Expr::Int { + text: "abcd".into(), + var: int_var, + }, + ) + .unwrap(); + + assert!(int_node_id == actual_node_id); + + let actual_node_id = nodes.wrap_with_caret(int_node_id, 3).unwrap(); + + assert!(caret_node_id == actual_node_id); + + nodes + }; + + let mut module = Module { + nodes: actual, + decls: Vec::new(), + carets: vec![caret_node_id], + selections: Vec::new(), + }; + + module.backspace(); + module.backspace(); + + assert_eq!(expected, module.nodes); +} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index fd3fa95305..3672701e41 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -6,6 +6,7 @@ use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; use winit::event_loop::ControlFlow; pub mod text_state; +pub mod ast; /// The editor is actually launched from the CLI if you pass it zero arguments, /// or if you provide it 1 or more files or directories to open on launch. From 673fc3bcc30fa0b7a8236ef4fd72039b9e774e44 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 31 May 2020 17:28:12 -0400 Subject: [PATCH 25/37] Add custom formatting for NodeId --- editor/src/ast.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/editor/src/ast.rs b/editor/src/ast.rs index c803d11690..cc123b48a4 100644 --- a/editor/src/ast.rs +++ b/editor/src/ast.rs @@ -1,4 +1,5 @@ use roc_types::subs::Variable; +use std::fmt; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Problem { @@ -10,7 +11,7 @@ pub type Res = Result; /// The index into a decl's array of nodes. /// This is a u32 index because no decl is allowed to hold more than 2^32 entries, /// and it saves space on 64-bit systems compared to a pointer. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct NodeId(u32); impl NodeId { @@ -21,6 +22,16 @@ impl NodeId { } } +impl fmt::Debug for NodeId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self == &NodeId::NONE { + write!(f, "none") + } else { + write!(f, "#{}", self.0) + } + } +} + #[derive(Debug, Default, PartialEq, Eq)] pub struct Nodes { nodes: Vec, From 020e2d70fde8478e1756babc61a8460cbeed4491 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 31 May 2020 17:28:23 -0400 Subject: [PATCH 26/37] Add PartialLookup (commented out for now) --- editor/src/ast.rs | 9 +++++++++ editor/src/lib.rs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/editor/src/ast.rs b/editor/src/ast.rs index cc123b48a4..bbb892f950 100644 --- a/editor/src/ast.rs +++ b/editor/src/ast.rs @@ -198,8 +198,17 @@ pub enum NodeContent { #[derive(Debug, PartialEq, Eq)] pub enum Expr { + /// An integer literal (without a dot) Int { text: String, var: Variable }, + /// An floating-point literal (with a dot) Float { text: String, var: Variable }, + // /// A partial lookup that has not yet been completed, e.g. + // /// `Foo.` or `pkg.Foo.Bar` + // PartialLookup { + // /// dot-separated sections, e.g. `Foo.Bar.` would be ["Foo", "Bar", ""] + // sections: Vec, + // var: Variable, + // }, // Lookup { // name: String, // var: Variable, diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 3672701e41..f003aa949e 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -5,8 +5,8 @@ use wgpu_glyph::{ab_glyph, GlyphBrushBuilder, Section, Text}; use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; use winit::event_loop::ControlFlow; -pub mod text_state; pub mod ast; +pub mod text_state; /// The editor is actually launched from the CLI if you pass it zero arguments, /// or if you provide it 1 or more files or directories to open on launch. From d9451a4b980c065a3eca473c0cc85cb7d0c6ff74 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 31 May 2020 18:14:34 -0400 Subject: [PATCH 27/37] cargo update --- Cargo.lock | 530 ++++++++++++++++++++++++++++------------------------- 1 file changed, 282 insertions(+), 248 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 048f436508..b8825622b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,10 +40,10 @@ dependencies = [ ] [[package]] -name = "android_glue" -version = "0.2.3" +name = "android_log-sys" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" +checksum = "b8052e2d8aabbb8d556d6abbcce2a22b9590996c5f849b9c7ce4544a2e3b984e" [[package]] name = "ansi_term" @@ -65,9 +65,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" +checksum = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62" [[package]] name = "arrayvec" @@ -81,7 +81,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69daec0742947f33a85931fa3cb0ce5f07929159dcbd1f0cbb5b2912e2978509" dependencies = [ - "libloading", + "libloading 0.5.2", ] [[package]] @@ -136,9 +136,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "bstr" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" +checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" dependencies = [ "lazy_static", "memchr", @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" +checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6" [[package]] name = "byteorder" @@ -186,9 +186,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.50" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" [[package]] name = "cfg-if" @@ -198,9 +198,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "clap" -version = "2.33.0" +version = "2.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" dependencies = [ "bitflags", "textwrap", @@ -231,9 +231,9 @@ source = "git+https://github.com/rtfeldman/clap#e1d83a78804a271b053d4d21f69b67f7 dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] @@ -247,29 +247,14 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.19.1" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29f7768b2d1be17b96158e3285951d366b40211320fb30826a76cb7a0da6400" +checksum = "8f7b6f3f7f4f0b3ec5c5039aaa9e8c3cef97a7a480a400fd62944841314f293d" dependencies = [ "bitflags", "block", - "core-foundation 0.6.4", - "core-graphics 0.17.3", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4736c86d51bd878b474400d9ec888156f4037015f5d09794fab9f26eab1ad4" -dependencies = [ - "bitflags", - "block", - "core-foundation 0.7.0", - "core-graphics 0.19.0", + "core-foundation", + "core-graphics", "foreign-types", "libc", "objc", @@ -281,50 +266,22 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127" -[[package]] -name = "core-foundation" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" -dependencies = [ - "core-foundation-sys 0.6.2", - "libc", -] - [[package]] name = "core-foundation" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" dependencies = [ - "core-foundation-sys 0.7.0", + "core-foundation-sys", "libc", ] -[[package]] -name = "core-foundation-sys" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" - [[package]] name = "core-foundation-sys" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" -[[package]] -name = "core-graphics" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" -dependencies = [ - "bitflags", - "core-foundation 0.6.4", - "foreign-types", - "libc", -] - [[package]] name = "core-graphics" version = "0.19.0" @@ -332,33 +289,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e78b2e0aaf43f08e7ae0d6bc96895ef72ff0921c7d4ff4762201b2dba376dd" dependencies = [ "bitflags", - "core-foundation 0.7.0", + "core-foundation", "foreign-types", "libc", ] [[package]] name = "core-video-sys" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc065219542086f72d1e9f7aadbbab0989e980263695d129d502082d063a9d0" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" dependencies = [ "cfg-if", - "core-foundation-sys 0.6.2", - "core-graphics 0.17.3", + "core-foundation-sys", + "core-graphics", "libc", "objc", ] [[package]] name = "criterion" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc755679c12bda8e5523a71e4d654b6bf2e14bd838dfc48cde6559a05caf7d1" +checksum = "63f696897c88b57f4ffe3c69d8e1a0613c7d0e6c4833363c8560fbde9c47b966" dependencies = [ "atty", "cast", - "clap 2.33.0", + "clap 2.33.1", "criterion-plot", "csv", "itertools", @@ -377,9 +334,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01e15e0ea58e8234f96146b1f91fa9d0e4dd7a38da93ff7a75d42c0b9d3a545" +checksum = "ddeaf7989f00f2e1d871a26a110f3ed713632feac17f65f03ca938c542618b60" dependencies = [ "cast", "itertools", @@ -423,9 +380,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -471,10 +428,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7ed48e89905e5e146bcc1951cc3facb9e44aea9adf5dc01078cda1bd24b662" dependencies = [ "bitflags", - "libloading", + "libloading 0.5.2", "winapi 0.3.8", ] +[[package]] +name = "derivative" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", +] + [[package]] name = "difference" version = "2.0.0" @@ -495,11 +463,11 @@ checksum = "6d9d8664cf849d7d0f3114a3a387d2f5e4303176d746d5a951aaddc66dfe9240" [[package]] name = "dlib" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e51249a9d823a4cb79e3eca6dcd756153e8ed0157b6c04775d04bf1b13b76a" +checksum = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76" dependencies = [ - "libloading", + "libloading 0.6.2", ] [[package]] @@ -539,9 +507,9 @@ dependencies = [ [[package]] name = "fnv" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" @@ -635,9 +603,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] @@ -695,17 +663,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gfx-auxil" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b46e6f0031330a0be08d17820f2dcaaa91cb36710a97a9500cb4f1c36e785c8" -dependencies = [ - "fxhash", - "gfx-hal", - "spirv_cross 0.18.0", -] - [[package]] name = "gfx-auxil" version = "0.4.0" @@ -714,7 +671,7 @@ checksum = "67bdbf8e8d6883c70e5a0d7379ad8ab3ac95127a3761306b36122d8f1c177a8e" dependencies = [ "fxhash", "gfx-hal", - "spirv_cross 0.20.0", + "spirv_cross", ] [[package]] @@ -724,34 +681,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92de0ddc0fde1a89b2a0e92dcc6bbb554bd34af0135e53a28d5ef064611094a4" dependencies = [ "bitflags", - "gfx-auxil 0.4.0", + "gfx-auxil", "gfx-hal", - "libloading", + "libloading 0.5.2", "log", "parking_lot", "range-alloc", "raw-window-handle", "smallvec", - "spirv_cross 0.20.0", + "spirv_cross", "winapi 0.3.8", "wio", ] [[package]] name = "gfx-backend-dx12" -version = "0.5.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6facbfcdbb383b3cb7ea0709932ad1273e600a31a242255e80597297ce803dca" +checksum = "708067c51793d2165a9d0c11e896c0365d2591753538c6b1b386ac9af1dab2ed" dependencies = [ "bitflags", "d3d12", - "gfx-auxil 0.3.0", + "gfx-auxil", "gfx-hal", "log", "range-alloc", "raw-window-handle", "smallvec", - "spirv_cross 0.18.0", + "spirv_cross", "winapi 0.3.8", ] @@ -767,18 +724,18 @@ dependencies = [ [[package]] name = "gfx-backend-metal" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe128c29675b5afc8acdda1dfe096d6abd5e3528059ab0b98bda8215d8beed9" +checksum = "205f3ca8e74ed814ea2c0206d47d8925077673cab2e21f9b12d48ff781cf87ee" dependencies = [ "arrayvec", "bitflags", "block", - "cocoa 0.20.0", + "cocoa", "copyless", - "core-graphics 0.19.0", + "core-graphics", "foreign-types", - "gfx-auxil 0.3.0", + "gfx-auxil", "gfx-hal", "lazy_static", "log", @@ -788,7 +745,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "smallvec", - "spirv_cross 0.18.0", + "spirv_cross", "storage-map", ] @@ -801,7 +758,7 @@ dependencies = [ "arrayvec", "ash", "byteorder", - "core-graphics 0.19.0", + "core-graphics", "gfx-hal", "lazy_static", "log", @@ -896,9 +853,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.10" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" +checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" dependencies = [ "libc", ] @@ -975,9 +932,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54554010aa3d17754e484005ea0022f1c93839aabc627c2c55f3d7b47206134c" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", "unindent", ] @@ -1013,9 +970,9 @@ checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a" [[package]] name = "instant" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7152d2aed88aa566e7a342250f21ba2222c1ae230ad577499dbfa3c18475b80" +checksum = "7777a24a1ce5de49fcdde84ec46efa487c3af49d5b6e6e0a50367cc5c1096182" [[package]] name = "iovec" @@ -1028,9 +985,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.8.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" dependencies = [ "either", ] @@ -1042,10 +999,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] -name = "js-sys" -version = "0.3.37" +name = "jni-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" dependencies = [ "wasm-bindgen", ] @@ -1074,9 +1037,9 @@ checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" [[package]] name = "libc" -version = "0.2.69" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" [[package]] name = "libloading" @@ -1088,6 +1051,15 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "libloading" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cadb8e769f070c45df05c78c7520eb4cd17061d4ab262e43cfc68b4d00ac71c" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "line_drawing" version = "0.7.0" @@ -1188,8 +1160,8 @@ checksum = "e198a0ee42bdbe9ef2c09d0b9426f3b2b47d90d93a4a9b0395c4cea605e92dc0" dependencies = [ "bitflags", "block", - "cocoa 0.20.0", - "core-graphics 0.19.0", + "cocoa", + "core-graphics", "foreign-types", "log", "objc", @@ -1197,9 +1169,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.6.21" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" dependencies = [ "cfg-if", "fuchsia-zircon", @@ -1234,15 +1206,15 @@ checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" dependencies = [ "log", "mio", - "miow 0.3.3", + "miow 0.3.4", "winapi 0.3.8", ] [[package]] name = "mio-uds" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" dependencies = [ "iovec", "libc", @@ -1263,19 +1235,50 @@ dependencies = [ [[package]] name = "miow" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" +checksum = "22dfdd1d51b2639a5abd17ed07005c3af05fb7a2a3b1a1d0d7af1000a520c1c7" dependencies = [ "socket2", "winapi 0.3.8", ] [[package]] -name = "net2" -version = "0.2.33" +name = "ndk" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +checksum = "95a356cafe20aee088789830bfea3a61336e84ded9e545e00d3869ce95dcb80c" +dependencies = [ + "jni-sys", + "ndk-sys", + "num_enum", +] + +[[package]] +name = "ndk-glue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1730ee2e3de41c3321160a6da815f008c4006d71b095880ea50e17cf52332b8" +dependencies = [ + "android_log-sys", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2820aca934aba5ed91c79acc72b6a44048ceacc5d36c035ed4e051f12d887d" + +[[package]] +name = "net2" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" dependencies = [ "cfg-if", "libc", @@ -1306,14 +1309,36 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ "hermit-abi", "libc", ] +[[package]] +name = "num_enum" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca565a7df06f3d4b485494f25ba05da1435950f4dc263440eda7a6fa9b8e36e4" +dependencies = [ + "derivative", + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d" +dependencies = [ + "proc-macro-crate", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", +] + [[package]] name = "objc" version = "0.2.7" @@ -1335,15 +1360,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" [[package]] name = "oorandom" -version = "11.1.0" +version = "11.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcec7c9c2a95cacc7cd0ecb89d8a8454eca13906f6deb55258ffff0adeb9405" +checksum = "94af325bc33c7f60191be4e2c984d48aaa21e2854f473b85398344b60c9b6358" [[package]] name = "ordered-float" @@ -1375,9 +1400,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" dependencies = [ "cfg-if", "cloudabi", @@ -1402,9 +1427,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb44a25c5bba983be0fc8592dfaf3e6d0935ce8be0c6b15b2a39507af34a926" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", "synstructure", "unicode-xid 0.2.0", ] @@ -1430,16 +1455,16 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] name = "pin-project-lite" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" +checksum = "9df32da11d84f3a7d70205549562966279adb900e080fad3dccd8e64afccf0ad" [[package]] name = "pin-utils" @@ -1455,9 +1480,9 @@ checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" [[package]] name = "plotters" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3bb8da247d27ae212529352020f3e5ee16e83c0c258061d27b08ab92675eeb" +checksum = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb" dependencies = [ "js-sys", "num-traits", @@ -1467,9 +1492,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "pretty_assertions" @@ -1481,6 +1506,15 @@ dependencies = [ "difference", ] +[[package]] +name = "proc-macro-crate" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e" +dependencies = [ + "toml", +] + [[package]] name = "proc-macro-error" version = "0.4.12" @@ -1488,9 +1522,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", "version_check", ] @@ -1500,18 +1534,18 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", "syn-mid", "version_check", ] [[package]] name = "proc-macro-hack" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro-nested" @@ -1530,9 +1564,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.10" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ "unicode-xid 0.2.0", ] @@ -1577,11 +1611,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.3" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" dependencies = [ - "proc-macro2 1.0.10", + "proc-macro2 1.0.18", ] [[package]] @@ -1742,9 +1776,9 @@ dependencies = [ [[package]] name = "range-alloc" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5927936723a9e8b715d37d7e4b390455087c4bdf25b9f702309460577b14f9" +checksum = "a871f1e45a3a3f0c73fb60343c811238bb5143a81642e27c2ac7aac27ff01a63" [[package]] name = "raw-window-handle" @@ -1796,9 +1830,9 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "regex" -version = "1.3.6" +version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" dependencies = [ "aho-corasick", "memchr", @@ -1817,9 +1851,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" [[package]] name = "remove_dir_all" @@ -2274,9 +2308,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "same-file" @@ -2310,26 +2344,26 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.106" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" +checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" [[package]] name = "serde_derive" -version = "1.0.106" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] name = "serde_json" -version = "1.0.51" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" +checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" dependencies = [ "itoa", "ryu", @@ -2364,9 +2398,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "smithay-client-toolkit" @@ -2396,17 +2430,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "spirv_cross" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "946216f8793f7199e3ea5b995ee8dc20a0ace1fcf46293a0ef4c17e1d046dbde" -dependencies = [ - "cc", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "spirv_cross" version = "0.20.0" @@ -2455,12 +2478,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.17" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" +checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", + "proc-macro2 1.0.18", + "quote 1.0.6", "unicode-xid 0.2.0", ] @@ -2470,9 +2493,9 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] @@ -2481,9 +2504,9 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", "unicode-xid 0.2.0", ] @@ -2536,9 +2559,9 @@ dependencies = [ [[package]] name = "tinytemplate" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a3c6667d3e65eb1bc3aed6fd14011c6cbc3a0665218ab7f5daf040b9ec371a" +checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f" dependencies = [ "serde", "serde_json", @@ -2546,9 +2569,9 @@ dependencies = [ [[package]] name = "tokio" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ef16d072d2b6dc8b4a56c70f5c5ced1a37752116f8e7c1e80c659aa7cb6713" +checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" dependencies = [ "bytes", "fnv", @@ -2564,6 +2587,15 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + [[package]] name = "ttf-parser" version = "0.6.1" @@ -2623,9 +2655,9 @@ checksum = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" [[package]] name = "vec_map" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "ven_ena" @@ -2655,9 +2687,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "void" @@ -2684,9 +2716,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" +checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2694,47 +2726,47 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" +checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" +checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" dependencies = [ - "quote 1.0.3", + "quote 1.0.6", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" +checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" +checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" [[package]] name = "wayland-client" @@ -2798,9 +2830,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.37" +version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" +checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" dependencies = [ "js-sys", "wasm-bindgen", @@ -2915,9 +2947,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi 0.3.8", ] @@ -2930,15 +2962,14 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winit" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc53342d3d1a3d57f3949e0692d93d5a8adb7814d8683cef4a09c2b550e94246" +checksum = "1e4ccbf7ddb6627828eace16cacde80fc6bf4dbb3469f88487262a02cf8e7862" dependencies = [ - "android_glue", "bitflags", - "cocoa 0.19.1", - "core-foundation 0.6.4", - "core-graphics 0.17.3", + "cocoa", + "core-foundation", + "core-graphics", "core-video-sys", "dispatch", "instant", @@ -2947,6 +2978,9 @@ dependencies = [ "log", "mio", "mio-extras", + "ndk", + "ndk-glue", + "ndk-sys", "objc", "parking_lot", "percent-encoding", @@ -3041,7 +3075,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" dependencies = [ - "proc-macro2 1.0.10", - "syn 1.0.17", + "proc-macro2 1.0.18", + "syn 1.0.30", "synstructure", ] From 99dcda86035f7715948ee2f9c7b5343483e15527 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 31 May 2020 20:13:43 -0400 Subject: [PATCH 28/37] Use inlinable_string 0.1 --- cli/Cargo.toml | 2 +- compiler/can/Cargo.toml | 2 +- compiler/fmt/Cargo.toml | 2 +- compiler/gen/Cargo.toml | 2 +- compiler/load/Cargo.toml | 2 +- compiler/module/Cargo.toml | 2 +- compiler/parse/Cargo.toml | 2 +- compiler/problem/Cargo.toml | 2 +- compiler/reporting/Cargo.toml | 2 +- compiler/types/Cargo.toml | 2 +- editor/Cargo.toml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 473f638c34..dd59b4fbb9 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -53,7 +53,7 @@ clap = { git = "https://github.com/rtfeldman/clap", branch = "master" } im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.2", features = ["collections"] } -inlinable_string = "0.1.0" +inlinable_string = "0.1" tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] } # NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index b652c82b45..4d5b7782bb 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -16,7 +16,7 @@ ven_graph = { path = "../../vendor/pathfinding" } im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.2", features = ["collections"] } -inlinable_string = "0.1.0" +inlinable_string = "0.1" [dev-dependencies] pretty_assertions = "0.5.1" diff --git a/compiler/fmt/Cargo.toml b/compiler/fmt/Cargo.toml index 9193d0f948..59dce02d63 100644 --- a/compiler/fmt/Cargo.toml +++ b/compiler/fmt/Cargo.toml @@ -15,7 +15,7 @@ roc_types = { path = "../types" } im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.2", features = ["collections"] } -inlinable_string = "0.1.0" +inlinable_string = "0.1" [dev-dependencies] pretty_assertions = "0.5.1" diff --git a/compiler/gen/Cargo.toml b/compiler/gen/Cargo.toml index 515280d995..095894228d 100644 --- a/compiler/gen/Cargo.toml +++ b/compiler/gen/Cargo.toml @@ -20,7 +20,7 @@ roc_mono = { path = "../mono" } im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.2", features = ["collections"] } -inlinable_string = "0.1.0" +inlinable_string = "0.1" # NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # # The reason for this fork is that the way Inkwell is designed, you have to use diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index 179f8bec67..f2aea40e27 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -18,7 +18,7 @@ roc_unify = { path = "../unify" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } bumpalo = { version = "3.2", features = ["collections"] } -inlinable_string = "0.1.0" +inlinable_string = "0.1" tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] } [dev-dependencies] diff --git a/compiler/module/Cargo.toml b/compiler/module/Cargo.toml index 47695cdecd..0cc70a1010 100644 --- a/compiler/module/Cargo.toml +++ b/compiler/module/Cargo.toml @@ -9,7 +9,7 @@ license = "Apache-2.0" roc_region = { path = "../region" } roc_collections = { path = "../collections" } bumpalo = { version = "3.2", features = ["collections"] } -inlinable_string = "0.1.0" +inlinable_string = "0.1" lazy_static = "1.4" [dev-dependencies] diff --git a/compiler/parse/Cargo.toml b/compiler/parse/Cargo.toml index 49c3e86efc..8d129df1e5 100644 --- a/compiler/parse/Cargo.toml +++ b/compiler/parse/Cargo.toml @@ -10,7 +10,7 @@ roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } bumpalo = { version = "3.2", features = ["collections"] } -inlinable_string = "0.1.0" +inlinable_string = "0.1" [dev-dependencies] pretty_assertions = "0.5.1" diff --git a/compiler/problem/Cargo.toml b/compiler/problem/Cargo.toml index 6b8ac711c8..a103480fd6 100644 --- a/compiler/problem/Cargo.toml +++ b/compiler/problem/Cargo.toml @@ -10,7 +10,7 @@ roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_parse = { path = "../parse" } -inlinable_string = "0.1.0" +inlinable_string = "0.1" [dev-dependencies] pretty_assertions = "0.5.1" diff --git a/compiler/reporting/Cargo.toml b/compiler/reporting/Cargo.toml index fbc3209589..fea377a8a2 100644 --- a/compiler/reporting/Cargo.toml +++ b/compiler/reporting/Cargo.toml @@ -17,7 +17,7 @@ roc_can = { path = "../can" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } ven_pretty = { path = "../../vendor/pretty" } -inlinable_string = "0.1.0" +inlinable_string = "0.1" im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! distance = "0.4.0" diff --git a/compiler/types/Cargo.toml b/compiler/types/Cargo.toml index 14cec69191..21765d30ac 100644 --- a/compiler/types/Cargo.toml +++ b/compiler/types/Cargo.toml @@ -12,7 +12,7 @@ roc_module = { path = "../module" } roc_parse = { path = "../parse" } roc_problem = { path = "../problem" } ven_ena = { path = "../../vendor/ena" } -inlinable_string = "0.1.0" +inlinable_string = "0.1" [dev-dependencies] pretty_assertions = "0.5.1" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index ddc22675b7..8af155eade 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -27,7 +27,7 @@ roc_reporting = { path = "../compiler/reporting" } im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.2", features = ["collections"] } -inlinable_string = "0.1.0" +inlinable_string = "0.1" tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] } # NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # From 3465f0a8be3c3a29390c923b152a91dde8443048 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 31 May 2020 20:13:54 -0400 Subject: [PATCH 29/37] Use inlinable_string in editor AST --- editor/src/ast.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/editor/src/ast.rs b/editor/src/ast.rs index bbb892f950..bed8885f41 100644 --- a/editor/src/ast.rs +++ b/editor/src/ast.rs @@ -1,3 +1,5 @@ +use inlinable_string::string_ext::StringExt; +use inlinable_string::InlinableString; use roc_types::subs::Variable; use std::fmt; @@ -199,18 +201,24 @@ pub enum NodeContent { #[derive(Debug, PartialEq, Eq)] pub enum Expr { /// An integer literal (without a dot) - Int { text: String, var: Variable }, + Int { + text: InlinableString, + var: Variable, + }, /// An floating-point literal (with a dot) - Float { text: String, var: Variable }, + Float { + text: InlinableString, + var: Variable, + }, // /// A partial lookup that has not yet been completed, e.g. // /// `Foo.` or `pkg.Foo.Bar` // PartialLookup { // /// dot-separated sections, e.g. `Foo.Bar.` would be ["Foo", "Bar", ""] - // sections: Vec, + // sections: Vec, // var: Variable, // }, // Lookup { - // name: String, + // name: InlinableString, // var: Variable, // }, // If { From 0861a51240d28cea778ec3e76a621eb488eeb9b5 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 31 May 2020 22:53:55 -0400 Subject: [PATCH 30/37] Update clippy settings for editor --- editor/src/lib.rs | 13 +++++++++++++ editor/src/text_state.rs | 1 - 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index f003aa949e..cc40041b12 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -1,3 +1,16 @@ +#![warn(clippy::all, clippy::dbg_macro)] +// I'm skeptical that clippy:large_enum_variant is a good lint to have globally enabled. +// +// It warns about a performance problem where the only quick remediation is +// to allocate more on the heap, which has lots of tradeoffs - including making it +// long-term unclear which allocations *need* to happen for compilation's sake +// (e.g. recursive structures) versus those which were only added to appease clippy. +// +// Effectively optimizing data struture memory layout isn't a quick fix, +// and encouraging shortcuts here creates bad incentives. I would rather temporarily +// re-enable this when working on performance optimizations than have it block PRs. +#![allow(clippy::large_enum_variant)] + use std::error::Error; use std::io; use std::path::Path; diff --git a/editor/src/text_state.rs b/editor/src/text_state.rs index 1f7e792ab6..42f6474482 100644 --- a/editor/src/text_state.rs +++ b/editor/src/text_state.rs @@ -60,7 +60,6 @@ pub fn handle_text_input( | WebBack | WebFavorites | WebForward | WebHome | WebRefresh | WebSearch | Apps | Tab | WebStop => { // TODO handle - dbg!(virtual_keycode); } Back => { text_state.pop(); From 0b6413454794866eb0fb2e04df917186dab0703b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 31 May 2020 22:54:01 -0400 Subject: [PATCH 31/37] Get basic undo/redo working --- editor/src/ast.rs | 222 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 204 insertions(+), 18 deletions(-) diff --git a/editor/src/ast.rs b/editor/src/ast.rs index bed8885f41..768b155421 100644 --- a/editor/src/ast.rs +++ b/editor/src/ast.rs @@ -34,7 +34,32 @@ impl fmt::Debug for NodeId { } } -#[derive(Debug, Default, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum UndoAction { + UndoEdit { + caret_id: NodeId, + caret_parent_id: NodeId, + caret_child_id: NodeId, + caret_offset: u16, + child: Node, + redo_action: Action, + }, + /// Used in undo logs to mean "the next N undo actions in the log should + /// be replayed together in a batch." + Multiple(u16), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Action { + Backspace, + Paste, + + /// Used in redo logs to mean "the next N redo actions in the log should + /// be replayed together in a batch." + Multiple(u16), +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct Nodes { nodes: Vec, } @@ -138,6 +163,17 @@ impl Nodes { } } + pub fn replace(&mut self, node_id: NodeId, node: Node) { + let elem = self.nodes.get_mut(node_id.as_index()).unwrap_or_else(|| { + panic!( + "Tried to replace element at nonexistant NodeId {:?} with {:?}", + node_id, node + ) + }); + + *elem = node; + } + pub fn wrap_with_caret(&mut self, target_id: NodeId, offset: u16) -> Res { let parent_id = self.get(target_id).parent_id; let caret_id = { @@ -182,13 +218,13 @@ impl Nodes { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Node { parent_id: NodeId, content: NodeContent, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum NodeContent { Expr(Expr /* TODO should Node have a Variable? */), // Pattern(Pattern), @@ -198,7 +234,7 @@ pub enum NodeContent { Removed, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Expr { /// An integer literal (without a dot) Int { @@ -237,28 +273,35 @@ pub enum Expr { // }, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Pattern { Identifier { text: String, var: Variable }, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Decl { Def(Def), } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Def { Body { pattern: NodeId, expr: NodeId }, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Module { pub nodes: Nodes, pub decls: Vec, // Use a Vec over a Set because it'll always be of small length pub carets: Vec, pub selections: Vec<(NodeId, NodeId)>, + + /// Because these actions store NodeId values, it's critically important + /// that when we take an action and then redo it, everything (including Nodes) + /// ends up back in the same state, including NodeId values. + pub undo_log: Vec, + + pub redos: Vec, } impl Node { @@ -329,11 +372,107 @@ impl Node { } impl Module { - pub fn backspace(&mut self) { - if self.nodes.is_empty() { - return; - } + pub fn undo(&mut self) { + let mut iterations_remaining: u16 = 0; + loop { + match self.undo_log.pop() { + Some(action) => { + iterations_remaining += self.apply_undo_action(action); + } + None => { + if iterations_remaining > 0 { + panic!("Expected to be able to do {} more Undo iterations, but ran out of actions to undo.", iterations_remaining); + } + } + } + + if iterations_remaining == 0 { + return; + } else { + iterations_remaining -= 1; + } + } + } + + pub fn redo(&mut self) { + let mut iterations_remaining: u16 = 0; + + loop { + match self.redos.pop() { + Some(action) => { + iterations_remaining += self.apply_action(action); + } + None => { + if iterations_remaining > 0 { + panic!("Expected to be able to do {} more Redo iterations, but ran out of actions to redo.", iterations_remaining); + } + } + } + + if iterations_remaining == 0 { + return; + } else { + iterations_remaining -= 1; + } + } + } + + fn apply_action(&mut self, action: Action) -> u16 { + use Action::*; + + match action { + Backspace => { + self.backspace(); + + 0 + } + Paste => { + todo!("TODO support Paste action"); + } + Multiple(iterations) => iterations, + } + } + + fn apply_undo_action(&mut self, action: UndoAction) -> u16 { + use UndoAction::*; + + match action { + UndoEdit { + caret_id, + caret_parent_id, + caret_child_id, + caret_offset, + child, + redo_action, + } => { + self.redos.push(redo_action); + self.nodes.replace(caret_child_id, child); + + let caret = Node { + parent_id: caret_parent_id, + content: NodeContent::Caret { + child_id: caret_child_id, + offset: caret_offset, + }, + }; + + self.nodes.replace(caret_id, caret); + + 0 + } + Multiple(iterations) => iterations, + } + } + + pub fn paste(&mut self, text: &str) { + todo!( + "TODO paste this string, taking carets and selections into account: {:?}", + text + ); + } + + pub fn backspace(&mut self) { for &caret_node_id in self.carets.iter() { debug_assert!(caret_node_id.as_index() <= self.nodes.len()); @@ -341,7 +480,6 @@ impl Module { // child node. Without these slices we'd need multiple simultaneous // mutable references to self.nodes, which is never allowed. let (before_caret, caret_and_after) = self.nodes.split_at_mut(caret_node_id); - dbg!(&caret_node_id, &before_caret, &caret_and_after); let (caret_only, after_caret) = caret_and_after.split_at_mut(1); let caret = caret_only.first_mut().unwrap(); @@ -371,6 +509,16 @@ impl Module { panic!("Could not get child node for caret {:?}", caret_node_id) }); + // Add an entry to the undo log to undo this edit. + self.undo_log.push(UndoAction::UndoEdit { + caret_id: caret_node_id, + caret_parent_id: parent_id, + caret_offset: offset_index, + caret_child_id: child_id, + child: child_node.clone(), + redo_action: Action::Backspace, + }); + // Mutate the child node to apply the backspace operation. // // -1 because index 0 is *before* the first character @@ -449,11 +597,31 @@ fn single_backspace_1_caret() { decls: Vec::new(), carets: vec![caret_node_id], selections: Vec::new(), + undo_log: Vec::new(), + redos: Vec::new(), }; + let original_module = module.clone(); + module.backspace(); + let altered_module = module.clone(); + assert_eq!(expected, module.nodes); + + module.undo(); + + let stashed_redos = module.redos; + + module.redos = Vec::new(); + + assert_eq!(original_module, module); + + module.redos = stashed_redos; + + module.redo(); + + assert_eq!(altered_module, module); } #[test] @@ -484,7 +652,6 @@ fn double_backspace_1_caret() { let actual = { let mut nodes = Nodes::default(); - let actual_node_id = nodes .push_expr( NodeId::NONE, @@ -496,10 +663,7 @@ fn double_backspace_1_caret() { .unwrap(); assert!(int_node_id == actual_node_id); - - let actual_node_id = nodes.wrap_with_caret(int_node_id, 3).unwrap(); - - assert!(caret_node_id == actual_node_id); + assert!(caret_node_id == nodes.wrap_with_caret(int_node_id, 3).unwrap()); nodes }; @@ -509,10 +673,32 @@ fn double_backspace_1_caret() { decls: Vec::new(), carets: vec![caret_node_id], selections: Vec::new(), + undo_log: Vec::new(), + redos: Vec::new(), }; + let original_module = module.clone(); + module.backspace(); module.backspace(); + let altered_module = module.clone(); + assert_eq!(expected, module.nodes); + + module.undo(); + module.undo(); + + let stashed_redos = module.redos; + + module.redos = Vec::new(); + + assert_eq!(original_module, module); + + module.redos = stashed_redos; + + module.redo(); + module.redo(); + + assert_eq!(altered_module, module); } From 97cb4a737e25a80a008e1cb1ea554e1aae49cb66 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Wed, 3 Jun 2020 18:56:23 -0400 Subject: [PATCH 32/37] List.single implementation --- compiler/builtins/src/std.rs | 6 +++ compiler/builtins/src/unique.rs | 22 ++++++++++ compiler/gen/src/llvm/build.rs | 70 +++++++++++++++++++++++++++++- compiler/gen/tests/gen_builtins.rs | 11 +++++ compiler/module/src/symbol.rs | 1 + 5 files changed, 109 insertions(+), 1 deletion(-) diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 936ebe9ea5..db3edf7173 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -507,6 +507,12 @@ pub fn types() -> MutMap { ), ); + // single : a -> List a + add_type( + Symbol::LIST_SINGLE, + SolvedType::Func(vec![flex(TVAR1)], Box::new(list_type(flex(TVAR1)))), + ); + // len : List * -> Int add_type( Symbol::LIST_LEN, diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index e75fe3398a..546747f06a 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -593,6 +593,28 @@ pub fn types() -> MutMap { ) }); + add_type(Symbol::LIST_SINGLE, { + let u = UVAR1; + let v = UVAR2; + let star = UVAR4; + + let a = TVAR1; + + unique_function( + vec![SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![disjunction(u, vec![v]), flex(a)], + )], + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + boolean(star), + SolvedType::Apply(Symbol::LIST_LIST, vec![attr_type(u, a)]), + ], + ), + ) + }); + // push : Attr (w | u | v) (List (Attr u a)) // , Attr (u | v) a // -> Attr * (List (Attr u a)) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index aca03ada10..5ba7350734 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -435,7 +435,7 @@ pub fn build_expr<'a, 'ctx, 'env>( if elems.is_empty() { let struct_type = collection(ctx, env.ptr_bytes); - // THe pointer should be null (aka zero) and the length should be zero, + // The pointer should be null (aka zero) and the length should be zero, // so the whole struct should be a const_zero BasicValueEnum::StructValue(struct_type.const_zero()) } else { @@ -1402,6 +1402,74 @@ fn call_with_args<'a, 'ctx, 'env>( Symbol::FLOAT_ROUND => call_intrinsic(LLVM_LROUND_I64_F64, env, args), Symbol::LIST_SET => list_set(parent, args, env, InPlace::Clone), Symbol::LIST_SET_IN_PLACE => list_set(parent, args, env, InPlace::InPlace), + Symbol::LIST_SINGLE => { + // List.single : a -> List a + debug_assert!(args.len() == 1); + + let (elem, elem_layout) = args[0]; + + let builder = env.builder; + let ctx = env.context; + + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; + + let ptr = { + let bytes_len = elem_bytes; + let len_type = env.ptr_int(); + let len = len_type.const_int(bytes_len, false); + + env.builder + .build_array_malloc(elem_type, len, "create_list_ptr") + .unwrap() + + // TODO check if malloc returned null; if so, runtime error for OOM! + }; + + // Put the element into the list + let elem_ptr = unsafe { + builder.build_in_bounds_gep( + ptr, + &[ctx.i32_type().const_int( + // 0 as in 0 index of our new list + 0 as u64, false, + )], + "index", + ) + }; + + builder.build_store(elem_ptr, elem); + + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr"); + let struct_type = collection(ctx, ptr_bytes); + let len = BasicValueEnum::IntValue(env.ptr_int().const_int(1, false)); + + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr_as_int, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") + .unwrap(); + + // + builder.build_bitcast( + struct_val.into_struct_value(), + collection(ctx, ptr_bytes), + "cast_collection", + ) + } Symbol::INT_DIV_UNSAFE => { debug_assert!(args.len() == 2); diff --git a/compiler/gen/tests/gen_builtins.rs b/compiler/gen/tests/gen_builtins.rs index 5098d62a35..72622d2732 100644 --- a/compiler/gen/tests/gen_builtins.rs +++ b/compiler/gen/tests/gen_builtins.rs @@ -489,6 +489,17 @@ mod gen_builtins { ); } + // #[test] + // fn list_push() { + // assert_evals_to!("List.push [] 1", &[1], &'static [i64]); + // } + + #[test] + fn list_single() { + assert_evals_to!("List.single 1", &[1], &'static [i64]); + assert_evals_to!("List.single 5.6", &[5.6], &'static [f64]); + } + #[test] fn empty_list_len() { with_larger_debug_stack(|| { diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 02709876f8..af54fd1ff8 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -691,6 +691,7 @@ define_builtins! { 14 LIST_CONCAT: "concat" 15 LIST_FIRST: "first" 16 LIST_FIRST_ARG: "first#list" + 17 LIST_SINGLE: "single" } 7 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias From 1ef85fa0614c7436b0dfd2c018df859ec518f1fa Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 6 Jun 2020 23:41:09 -0400 Subject: [PATCH 33/37] Store builtins as a Map --- compiler/can/src/builtins.rs | 37 ++++++++++++++++----------------- compiler/can/src/lib.rs | 3 +++ compiler/collections/src/all.rs | 18 ++++++++++++++++ 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 55feccc707..ebabf00e26 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,7 +1,6 @@ use crate::def::Def; -use crate::expr::Expr; -use crate::expr::Recursive; -use roc_collections::all::SendMap; +use crate::expr::{Expr, Recursive}; +use roc_collections::all::{MutMap, SendMap}; use roc_module::ident::TagName; use roc_module::operator::CalledVia; use roc_module::symbol::Symbol; @@ -25,22 +24,22 @@ use roc_types::subs::{VarStore, Variable}; /// delegates to the compiler-internal List.getUnsafe function to do the actual /// lookup (if the bounds check passed). That internal function is hardcoded in code gen, /// which works fine because it doesn't involve any open tag unions. -pub fn builtin_defs(var_store: &VarStore) -> Vec { - vec![ - list_get(var_store), - list_first(var_store), - int_div(var_store), - int_abs(var_store), - int_rem(var_store), - int_is_odd(var_store), - int_is_even(var_store), - int_is_zero(var_store), - int_is_positive(var_store), - int_is_negative(var_store), - float_is_positive(var_store), - float_is_negative(var_store), - float_is_zero(var_store), - ] +pub fn builtin_defs(var_store: &VarStore) -> MutMap { + mut_map! { + Symbol::LIST_GET => list_get(var_store), + Symbol::LIST_FIRST => list_first(var_store), + Symbol::INT_DIV => int_div(var_store), + Symbol::INT_ABS => int_abs(var_store), + Symbol::INT_REM => int_rem(var_store), + Symbol::INT_IS_ODD => int_is_odd(var_store), + Symbol::INT_IS_EVEN => int_is_even(var_store), + Symbol::INT_IS_ZERO => int_is_zero(var_store), + Symbol::INT_IS_POSITIVE => int_is_positive(var_store), + Symbol::INT_IS_NEGATIVE => int_is_negative(var_store), + Symbol::FLOAT_IS_POSITIVE => float_is_positive(var_store), + Symbol::FLOAT_IS_NEGATIVE => float_is_negative(var_store), + Symbol::FLOAT_IS_ZERO => float_is_zero(var_store), + } } /// Float.isZero : Float -> Bool diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index 44c7bf3faa..d5c81f482a 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -24,3 +24,6 @@ pub mod pattern; pub mod procedure; pub mod scope; pub mod string; + +#[macro_use] +extern crate roc_collections; diff --git a/compiler/collections/src/all.rs b/compiler/collections/src/all.rs index 91e2717f10..cc3fba6649 100644 --- a/compiler/collections/src/all.rs +++ b/compiler/collections/src/all.rs @@ -134,3 +134,21 @@ fn int_to_ordinal(number: usize) -> std::string::String { format!("{}{}", number, ending) } + +#[macro_export] +macro_rules! mut_map { + (@single $($x:tt)*) => (()); + (@count $($rest:expr),*) => (<[()]>::len(&[$(mut_map!(@single $rest)),*])); + + ($($key:expr => $value:expr,)+) => { mut_map!($($key => $value),+) }; + ($($key:expr => $value:expr),*) => { + { + let _cap = mut_map!(@count $($key),*); + let mut _map = ::std::collections::HashMap::with_capacity_and_hasher(_cap, $crate::all::default_hasher()); + $( + let _ = _map.insert($key, $value); + )* + _map + } + }; +} From 093579780f19ca00b9c43d91ce0b42555c00c3ad Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 6 Jun 2020 23:45:20 -0400 Subject: [PATCH 34/37] Fix stack overflow --- compiler/gen/tests/helpers/mod.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/compiler/gen/tests/helpers/mod.rs b/compiler/gen/tests/helpers/mod.rs index 30302c84a6..c1f992c156 100644 --- a/compiler/gen/tests/helpers/mod.rs +++ b/compiler/gen/tests/helpers/mod.rs @@ -245,16 +245,19 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut // since we aren't using modules here. let builtin_defs = roc_can::builtins::builtin_defs(&var_store); - for def in builtin_defs { - with_builtins = Expr::LetNonRec( - Box::new(def), - Box::new(Located { - region: Region::zero(), - value: with_builtins, - }), - var_store.fresh(), - SendMap::default(), - ); + for (symbol, def) in builtin_defs { + if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol) + { + with_builtins = Expr::LetNonRec( + Box::new(def), + Box::new(Located { + region: Region::zero(), + value: with_builtins, + }), + var_store.fresh(), + SendMap::default(), + ); + } } let loc_expr = Located { From e726d838ea1f4cfdb1e3026c0488c8ebb6f05ece Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 6 Jun 2020 23:55:05 -0400 Subject: [PATCH 35/37] Don't have builtins return Defs --- compiler/can/src/builtins.rs | 45 +++++++++++++------------------ compiler/gen/tests/helpers/mod.rs | 18 +++++++++++-- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index ebabf00e26..c978d3330e 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,6 +1,5 @@ -use crate::def::Def; use crate::expr::{Expr, Recursive}; -use roc_collections::all::{MutMap, SendMap}; +use roc_collections::all::MutMap; use roc_module::ident::TagName; use roc_module::operator::CalledVia; use roc_module::symbol::Symbol; @@ -24,7 +23,7 @@ use roc_types::subs::{VarStore, Variable}; /// delegates to the compiler-internal List.getUnsafe function to do the actual /// lookup (if the bounds check passed). That internal function is hardcoded in code gen, /// which works fine because it doesn't involve any open tag unions. -pub fn builtin_defs(var_store: &VarStore) -> MutMap { +pub fn builtin_defs(var_store: &VarStore) -> MutMap { mut_map! { Symbol::LIST_GET => list_get(var_store), Symbol::LIST_FIRST => list_first(var_store), @@ -43,7 +42,7 @@ pub fn builtin_defs(var_store: &VarStore) -> MutMap { } /// Float.isZero : Float -> Bool -fn float_is_zero(var_store: &VarStore) -> Def { +fn float_is_zero(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -62,7 +61,7 @@ fn float_is_zero(var_store: &VarStore) -> Def { } /// Float.isNegative : Float -> Bool -fn float_is_negative(var_store: &VarStore) -> Def { +fn float_is_negative(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -81,7 +80,7 @@ fn float_is_negative(var_store: &VarStore) -> Def { } /// Float.isPositive : Float -> Bool -fn float_is_positive(var_store: &VarStore) -> Def { +fn float_is_positive(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -100,7 +99,7 @@ fn float_is_positive(var_store: &VarStore) -> Def { } /// Int.isNegative : Int -> Bool -fn int_is_negative(var_store: &VarStore) -> Def { +fn int_is_negative(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -116,7 +115,7 @@ fn int_is_negative(var_store: &VarStore) -> Def { } /// Int.isPositive : Int -> Bool -fn int_is_positive(var_store: &VarStore) -> Def { +fn int_is_positive(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -132,7 +131,7 @@ fn int_is_positive(var_store: &VarStore) -> Def { } /// Int.isZero : Int -> Bool -fn int_is_zero(var_store: &VarStore) -> Def { +fn int_is_zero(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -148,7 +147,7 @@ fn int_is_zero(var_store: &VarStore) -> Def { } /// Int.isOdd : Int -> Bool -fn int_is_odd(var_store: &VarStore) -> Def { +fn int_is_odd(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -171,7 +170,7 @@ fn int_is_odd(var_store: &VarStore) -> Def { } /// Int.isEven : Int -> Bool -fn int_is_even(var_store: &VarStore) -> Def { +fn int_is_even(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -194,7 +193,7 @@ fn int_is_even(var_store: &VarStore) -> Def { } /// List.get : List elem, Int -> Result elem [ OutOfBounds ]* -fn list_get(var_store: &VarStore) -> Def { +fn list_get(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -265,7 +264,7 @@ fn list_get(var_store: &VarStore) -> Def { } /// Int.rem : Int, Int -> Int -fn int_rem(var_store: &VarStore) -> Def { +fn int_rem(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -312,7 +311,7 @@ fn int_rem(var_store: &VarStore) -> Def { } /// Int.abs : Int -> Int -fn int_abs(var_store: &VarStore) -> Def { +fn int_abs(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -349,7 +348,7 @@ fn int_abs(var_store: &VarStore) -> Def { } /// Int.div : Int, Int -> Result Int [ DivByZero ]* -fn int_div(var_store: &VarStore) -> Def { +fn int_div(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -408,7 +407,7 @@ fn int_div(var_store: &VarStore) -> Def { } /// List.first : List elem -> Result elem [ ListWasEmpty ]* -fn list_first(var_store: &VarStore) -> Def { +fn list_first(var_store: &VarStore) -> Expr { use crate::expr::Expr::*; defn( @@ -500,7 +499,7 @@ fn call(symbol: Symbol, args: Vec, var_store: &VarStore) -> Expr { } #[inline(always)] -fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Def { +fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Expr { use crate::expr::Expr::*; use crate::pattern::Pattern::*; @@ -509,19 +508,11 @@ fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> .map(|symbol| (var_store.fresh(), no_region(Identifier(symbol)))) .collect(); - let expr = Closure( + Closure( var_store.fresh(), fn_name, Recursive::NotRecursive, closure_args, Box::new((no_region(body), var_store.fresh())), - ); - - Def { - loc_pattern: no_region(Identifier(fn_name)), - loc_expr: no_region(expr), - expr_var: var_store.fresh(), - pattern_vars: SendMap::default(), - annotation: None, - } + ) } diff --git a/compiler/gen/tests/helpers/mod.rs b/compiler/gen/tests/helpers/mod.rs index c1f992c156..8eb384b750 100644 --- a/compiler/gen/tests/helpers/mod.rs +++ b/compiler/gen/tests/helpers/mod.rs @@ -6,10 +6,12 @@ pub mod eval; use self::bumpalo::Bump; use roc_builtins::unique::uniq_stdlib; use roc_can::constraint::Constraint; +use roc_can::def::Def; use roc_can::env::Env; use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::operator; +use roc_can::pattern::Pattern; use roc_can::scope::Scope; use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; use roc_constrain::expr::constrain_expr; @@ -245,11 +247,23 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut // since we aren't using modules here. let builtin_defs = roc_can::builtins::builtin_defs(&var_store); - for (symbol, def) in builtin_defs { + for (symbol, expr) in builtin_defs { if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol) { with_builtins = Expr::LetNonRec( - Box::new(def), + Box::new(Def { + loc_pattern: Located { + region: Region::zero(), + value: Pattern::Identifier(symbol), + }, + loc_expr: Located { + region: Region::zero(), + value: expr, + }, + expr_var: var_store.fresh(), + pattern_vars: SendMap::default(), + annotation: None, + }), Box::new(Located { region: Region::zero(), value: with_builtins, From 72706a0ac0d70993196bae03bbd7f3e695dee2b1 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 7 Jun 2020 15:37:12 -0400 Subject: [PATCH 36/37] clippy --- compiler/types/src/boolean_algebra.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/types/src/boolean_algebra.rs b/compiler/types/src/boolean_algebra.rs index 1e2b24914e..6e53046a66 100644 --- a/compiler/types/src/boolean_algebra.rs +++ b/compiler/types/src/boolean_algebra.rs @@ -74,7 +74,7 @@ impl Bool { for atom in &self.1 { if let Variable(v) = atom { - result.insert(v.clone()); + result.insert(*v); } } @@ -152,7 +152,7 @@ impl Bool { if let Variable(v) = atom { new_bound.insert(Variable(f(*v))); } else { - new_bound.insert(atom.clone()); + new_bound.insert(*atom); } } Bool(new_free, new_bound) From a4338f0406665e0be529511219f0577e993294a6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 7 Jun 2020 15:43:06 -0400 Subject: [PATCH 37/37] Remove lots of unnecessary clones (thanks clippy!) --- compiler/can/src/def.rs | 30 ++++++++++---------- compiler/can/src/expr.rs | 8 +++--- compiler/can/src/pattern.rs | 4 +-- compiler/constrain/src/pattern.rs | 8 +++--- compiler/constrain/src/uniq.rs | 11 ++++--- compiler/reporting/src/error/canonicalize.rs | 2 +- compiler/solve/src/solve.rs | 8 +++--- compiler/uniq/src/sharing.rs | 2 +- 8 files changed, 36 insertions(+), 37 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index d4835a78dc..34fb966474 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -358,8 +358,8 @@ pub fn sort_can_defs( let mut defined_symbols_set: ImSet = ImSet::default(); for symbol in can_defs_by_symbol.keys().into_iter() { - defined_symbols.push(symbol.clone()); - defined_symbols_set.insert(symbol.clone()); + defined_symbols.push(*symbol); + defined_symbols_set.insert(*symbol); } // Use topological sort to reorder the defs based on their dependencies to one another. @@ -688,7 +688,7 @@ fn pattern_to_vars_by_symbol( use Pattern::*; match pattern { Identifier(symbol) => { - vars_by_symbol.insert(symbol.clone(), expr_var); + vars_by_symbol.insert(*symbol, expr_var); } AppliedTag { arguments, .. } => { @@ -699,7 +699,7 @@ fn pattern_to_vars_by_symbol( RecordDestructure { destructs, .. } => { for destruct in destructs { - vars_by_symbol.insert(destruct.value.symbol.clone(), destruct.value.var); + vars_by_symbol.insert(destruct.value.symbol, destruct.value.var); } } @@ -947,7 +947,7 @@ fn canonicalize_pending_def<'a>( ) = ( &loc_pattern.value, &loc_can_pattern.value, - &loc_can_expr.value.clone(), + &loc_can_expr.value, ) { is_closure = true; @@ -964,7 +964,7 @@ fn canonicalize_pending_def<'a>( // closures don't have a name, and therefore pick a fresh symbol. But in this // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` // and we want to reference it by that name. - env.closures.insert(defined_symbol.clone(), references); + env.closures.insert(*defined_symbol, references); // The closure is self tail recursive iff it tail calls itself (by defined name). let is_recursive = match can_output.tail_call { @@ -975,7 +975,7 @@ fn canonicalize_pending_def<'a>( // Recursion doesn't count as referencing. (If it did, all recursive functions // would result in circular def errors!) refs_by_symbol - .entry(defined_symbol.clone()) + .entry(*defined_symbol) .and_modify(|(_, refs)| { refs.lookups = refs.lookups.without(defined_symbol); }); @@ -1008,7 +1008,7 @@ fn canonicalize_pending_def<'a>( }; refs_by_symbol.insert( - symbol.clone(), + *symbol, ( Located { value: ident.clone(), @@ -1055,7 +1055,7 @@ fn canonicalize_pending_def<'a>( env.tailcallable_symbol = Some(*defined_symbol); // TODO isn't types_by_symbol enough? Do we need vars_by_symbol too? - vars_by_symbol.insert(defined_symbol.clone(), expr_var); + vars_by_symbol.insert(*defined_symbol, expr_var); }; let (mut loc_can_expr, can_output) = @@ -1080,7 +1080,7 @@ fn canonicalize_pending_def<'a>( ) = ( &loc_pattern.value, &loc_can_pattern.value, - &loc_can_expr.value.clone(), + &loc_can_expr.value, ) { is_closure = true; @@ -1097,7 +1097,7 @@ fn canonicalize_pending_def<'a>( // closures don't have a name, and therefore pick a fresh symbol. But in this // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` // and we want to reference it by that name. - env.closures.insert(defined_symbol.clone(), references); + env.closures.insert(*defined_symbol, references); // The closure is self tail recursive iff it tail calls itself (by defined name). let is_recursive = match can_output.tail_call { @@ -1108,7 +1108,7 @@ fn canonicalize_pending_def<'a>( // Recursion doesn't count as referencing. (If it did, all recursive functions // would result in circular def errors!) refs_by_symbol - .entry(defined_symbol.clone()) + .entry(*defined_symbol) .and_modify(|(_, refs)| { refs.lookups = refs.lookups.without(defined_symbol); }); @@ -1145,7 +1145,7 @@ fn canonicalize_pending_def<'a>( }); refs_by_symbol.insert( - symbol.clone(), + symbol, ( Located { value: ident.clone().into(), @@ -1259,7 +1259,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap) -> if let Some(references) = closures.get(&symbol) { for v in &references.calls { - stack.push(v.clone()); + stack.push(*v); } // while there are symbols left to visit @@ -1275,7 +1275,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap) -> if let Some(nested_references) = closures.get(&nested_symbol) { // add its called to the stack for v in &nested_references.calls { - stack.push(v.clone()); + stack.push(*v); } } visited.insert(nested_symbol); diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 22d627824d..ef28706d93 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -450,7 +450,7 @@ pub fn canonicalize_expr<'a>( } } - env.register_closure(symbol.clone(), output.references.clone()); + env.register_closure(symbol, output.references.clone()); ( Closure( @@ -818,7 +818,7 @@ where answer = answer.union(other_refs); } - answer.lookups.insert(local.clone()); + answer.lookups.insert(*local); } for call in refs.calls.iter() { @@ -828,7 +828,7 @@ where answer = answer.union(other_refs); } - answer.calls.insert(call.clone()); + answer.calls.insert(*call); } answer @@ -860,7 +860,7 @@ where answer = answer.union(other_refs); } - answer.lookups.insert(closed_over_local.clone()); + answer.lookups.insert(*closed_over_local); } for call in references.calls.iter() { diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index d068ce19cd..7911f2a3b8 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -57,7 +57,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { match pattern { Identifier(symbol) => { - symbols.push(symbol.clone()); + symbols.push(*symbol); } AppliedTag { arguments, .. } => { @@ -67,7 +67,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { } RecordDestructure { destructs, .. } => { for destruct in destructs { - symbols.push(destruct.value.symbol.clone()); + symbols.push(destruct.value.symbol); } } diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 332104561e..2340175905 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -47,7 +47,7 @@ fn headers_from_annotation_help( ) -> bool { match pattern { Identifier(symbol) => { - headers.insert(symbol.clone(), annotation.clone()); + headers.insert(*symbol, annotation.clone()); true } Underscore @@ -64,7 +64,7 @@ fn headers_from_annotation_help( // NOTE ignores the .guard field. if let Some(field_type) = fields.get(&destruct.value.label) { headers.insert( - destruct.value.symbol.clone(), + destruct.value.symbol, Located::at(annotation.region, field_type.clone()), ); } else { @@ -123,7 +123,7 @@ pub fn constrain_pattern( Identifier(symbol) => { state.headers.insert( - symbol.clone(), + *symbol, Located { region, value: expected.get_type(), @@ -197,7 +197,7 @@ pub fn constrain_pattern( if !state.headers.contains_key(&symbol) { state .headers - .insert(symbol.clone(), Located::at(region, pat_type.clone())); + .insert(*symbol, Located::at(region, pat_type.clone())); } field_types.insert(label.clone(), pat_type.clone()); diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index 0f023a1363..7ff46fdd5a 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -148,7 +148,7 @@ fn constrain_pattern( match &pattern.value { Identifier(symbol) => { state.headers.insert( - symbol.clone(), + *symbol, Located { region: pattern.region, value: expected.get_type(), @@ -223,10 +223,9 @@ fn constrain_pattern( let expected = PExpected::NoExpectation(pat_type.clone()); if !state.headers.contains_key(&symbol) { - state.headers.insert( - symbol.clone(), - Located::at(pattern.region, pat_type.clone()), - ); + state + .headers + .insert(*symbol, Located::at(pattern.region, pat_type.clone())); } field_types.insert(label.clone(), pat_type.clone()); @@ -1391,7 +1390,7 @@ fn constrain_var( Lookup(symbol_for_lookup, expected, region) } Some(Usage::Access(_, _, _)) | Some(Usage::Update(_, _, _)) => { - applied_usage_constraint.insert(symbol_for_lookup.clone()); + applied_usage_constraint.insert(symbol_for_lookup); let mut variables = Vec::new(); let (free, rest, inner_type) = diff --git a/compiler/reporting/src/error/canonicalize.rs b/compiler/reporting/src/error/canonicalize.rs index 9d691a7ffb..9710ab32d5 100644 --- a/compiler/reporting/src/error/canonicalize.rs +++ b/compiler/reporting/src/error/canonicalize.rs @@ -280,7 +280,7 @@ fn pretty_runtime_error<'b>( if idents.is_empty() { alloc .reflow("The ") - .append(alloc.ident(first.value.clone())) + .append(alloc.ident(first.value)) .append(alloc.reflow( " value is defined directly in terms of itself, causing an infinite loop.", )) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 89da188fa2..0e621891cb 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -333,7 +333,7 @@ fn solve( let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); local_def_vars.insert( - symbol.clone(), + *symbol, Located { value: var, region: loc_type.region, @@ -344,7 +344,7 @@ fn solve( let mut new_env = env.clone(); for (symbol, loc_var) in local_def_vars.iter() { if !new_env.vars_by_symbol.contains_key(&symbol) { - new_env.vars_by_symbol.insert(symbol.clone(), loc_var.value); + new_env.vars_by_symbol.insert(*symbol, loc_var.value); } } @@ -398,7 +398,7 @@ fn solve( type_to_var(subs, next_rank, next_pools, cached_aliases, &def_type); local_def_vars.insert( - symbol.clone(), + *symbol, Located { value: var, region: loc_type.region, @@ -469,7 +469,7 @@ fn solve( for (symbol, loc_var) in local_def_vars.iter() { if !new_env.vars_by_symbol.contains_key(&symbol) { - new_env.vars_by_symbol.insert(symbol.clone(), loc_var.value); + new_env.vars_by_symbol.insert(*symbol, loc_var.value); } } diff --git a/compiler/uniq/src/sharing.rs b/compiler/uniq/src/sharing.rs index e81a621c71..3b8468fad9 100644 --- a/compiler/uniq/src/sharing.rs +++ b/compiler/uniq/src/sharing.rs @@ -416,7 +416,7 @@ impl Composable for VarUsage { } }; - self.usage.insert(symbol.clone(), value); + self.usage.insert(*symbol, value); } } }