Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ephemeral caches on BEAM #24

Merged
merged 3 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,53 @@ on:
pull_request:

jobs:
test:
test-sketch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: "26.0.2"
gleam-version: "1.0.0"
rebar3-version: "3"
otp-version: '26.0.2'
gleam-version: '1.4.1'
rebar3-version: '3'
# elixir-version: "1.15.4"
- run: gleam deps download
working-directory: sketch
- run: gleam test
working-directory: sketch
- run: gleam format --check src test
working-directory: sketch

test-sketch-css:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: '26.0.2'
gleam-version: '1.4.1'
rebar3-version: '3'
# elixir-version: "1.15.4"
- run: gleam deps download
working-directory: sketch_css
- run: gleam test
working-directory: sketch_css
- run: gleam format --check src test
working-directory: sketch_css

test-sketch-lustr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: '26.0.2'
gleam-version: '1.4.1'
rebar3-version: '3'
# elixir-version: "1.15.4"
- run: gleam deps download
working-directory: sketch_lustre
- run: gleam test
working-directory: sketch_lustre
- run: gleam format --check src test
working-directory: sketch_lustre
4 changes: 1 addition & 3 deletions e2e/shared_view/src/shared_view.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ pub type Msg {
}

/// Defines the standard app, used everywhere in Lustre applications.
/// For cross-technologies applications, only persistent cache can be used,
/// because BEAM does not support ephemeral cache at the moment.
pub fn app() {
let assert Ok(cache) = sketch.cache(strategy: sketch.Persistent)
let assert Ok(cache) = sketch.cache(strategy: sketch.Ephemeral)
sketch_lustre.node()
|> sketch_lustre.compose(view, cache)
|> lustre.simple(fn(_) { 0 }, update, _)
Expand Down
7 changes: 3 additions & 4 deletions e2e/ssr/manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ packages = [
{ name = "gramps", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "3CCAA6E081225180D95C79679D383BBF51C8D1FDC1B84DA1DA444F628C373793" },
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
{ name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" },
{ name = "lustre", version = "4.3.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "A0010C17CE8C847A2B979CE9FE68FB94AE9D75247D1173594C10B7C2FD3C83F2" },
{ name = "lustre", version = "4.3.2", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "7F4CE2DB524A882F97C94AA3D320986B6CAC415146D5F98DE4782E1B0B59098F" },
{ name = "mist", version = "1.2.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "109B4D64E68C104CC23BB3CC5441ECD479DD7444889DA01113B75C6AF0F0E17B" },
{ name = "plinth", version = "0.4.10", build_tools = ["gleam"], requirements = ["conversation", "gleam_javascript", "gleam_json", "gleam_stdlib"], otp_app = "plinth", source = "hex", outer_checksum = "01561989B292CEF20C864B42E9A32FD15F3F3E46E7B0A2142727A3C556B16EF3" },
{ name = "plinth", version = "0.4.11", build_tools = ["gleam"], requirements = ["conversation", "gleam_javascript", "gleam_json", "gleam_stdlib"], otp_app = "plinth", source = "hex", outer_checksum = "698B53538B02B261ABF30203F0B487B9C3BE2A1B40E858176ED0918AA1B61965" },
{ name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" },
{ name = "shared_view", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre", "sketch", "sketch_lustre"], source = "local", path = "../shared_view" },
{ name = "sketch", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], source = "local", path = "../../sketch" },
{ name = "sketch", version = "3.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], source = "local", path = "../../sketch" },
{ name = "sketch_lustre", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre", "plinth", "sketch"], source = "local", path = "../../sketch_lustre" },
{ name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" },
{ name = "xxhash", version = "0.3.1", build_tools = ["mix"], requirements = [], otp_app = "xxhash", source = "hex", outer_checksum = "14FEF02F550C1B393DE52912F7EF4CE53B5749C63DB70FFDC6582474E625513C" },
]

[requirements]
Expand Down
2 changes: 1 addition & 1 deletion e2e/ssr/src/ssr.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import sketch
import sketch/lustre as sketch_lustre

pub fn main() {
let assert Ok(cache) = sketch.cache(strategy: sketch.Persistent)
let assert Ok(cache) = sketch.cache(strategy: sketch.Ephemeral)
let assert Ok(_) =
fn(_) { greet(cache) }
|> mist.new()
Expand Down
3 changes: 1 addition & 2 deletions sketch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ import sketch
import sketch/lustre as sketch_lustre

pub fn main() {
// Initialise the cache. Two strategies can be used in browser, only one
// on server-side.
// Initialise the cache. Two strategies can be used. Ephemeral caches are designed as throw-away caches.
let assert Ok(cache) = sketch.cache(strategy: sketch.Ephemeral)
// Select the output of the generated stylesheet.
sketch_lustre.node()
Expand Down
40 changes: 18 additions & 22 deletions sketch/src/sketch.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import gleam/float
import gleam/int
import gleam/list
import gleam/pair
import gleam/result
import gleam/string
import sketch/internals/cache/setup as cache
Expand Down Expand Up @@ -33,41 +34,30 @@ pub fn class(styles: List(style.Style)) -> Class {
style.class(styles)
}

@target(erlang)
/// Render the content in the cache in proper CSS stylesheet.
pub fn render(cache: Cache) {
let assert BeamCache(cache) = cache
cache.render(cache)
}

@target(javascript)
pub fn render(cache: Cache) {
let assert JsCache(cache) = cache
style.render(cache)
case cache {
BeamCache(cache:) -> cache.render(cache)
JsCache(cache:) -> style.render(cache)
}
}

@target(javascript)
/// Convert a `Class` to its proper class name, to use it anywhere in your
/// application. It can have the form `class1` or `class1 class2` in case of
/// classes composition.
pub fn class_name(class: Class, cache: Cache) -> #(Cache, String) {
let assert JsCache(cache) = cache
let #(cache, class_name) = style.class_name(class, cache)
#(JsCache(cache), class_name)
}

@target(erlang)
pub fn class_name(class: Class, cache: Cache) -> #(Cache, String) {
let assert BeamCache(cache) = cache
#(BeamCache(cache), cache.class_name(class, cache))
case cache {
JsCache(c) -> style.class_name(class, c) |> pair.map_first(JsCache)
BeamCache(c) -> cache.class_name(class, c) |> pair.map_first(BeamCache)
}
}

/// Strategy for the Cache. Two strategies are available as of now: ephemeral
/// and persistent. In the first case, the cache is throwable, and every class
/// generation wil rely on hashing function. It means two class names will be
/// identical if their content are identical.
/// In the second case, the cache is persistent, meaning it will keep the
/// memories of the generated classes. On BEAM, only Persistent is allowed.
/// memories of the generated classes.
pub type Strategy {
Ephemeral
Persistent
Expand All @@ -76,6 +66,7 @@ pub type Strategy {
@target(javascript)
/// Create a cache, managing the styles. You can instanciate as much cache as
/// you want, if you need to manage different stylesheets.
/// Instanciating an `Ephemeral` _always_ succeed.
pub fn cache(strategy strategy: Strategy) {
Ok(case strategy {
Ephemeral -> JsCache(style.ephemeral())
Expand All @@ -84,8 +75,13 @@ pub fn cache(strategy strategy: Strategy) {
}

@target(erlang)
pub fn cache(strategy _strategy: Strategy) {
cache.persistent() |> result.map(BeamCache)
pub fn cache(strategy strategy: Strategy) {
case strategy {
Ephemeral -> Ok(BeamCache(cache.ephemeral()))
Persistent ->
cache.persistent()
|> result.map(BeamCache)
}
}

// Properties
Expand Down
43 changes: 31 additions & 12 deletions sketch/src/sketch/internals/cache/setup.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,53 @@ import gleam/bool
import gleam/erlang/process.{type Subject}
import gleam/list
import gleam/otp/actor
import gleam/pair
import gleam/result
import sketch/internals/cache/state
import sketch/internals/style

/// Manages the styles. Can be instanciated with [`create_cache`](#create_cache).
pub opaque type Cache {
Cache(subject: Subject(state.Request))
PersistentCache(subject: Subject(state.Request))
EphemeralCache(cache: style.Cache)
}

@target(erlang)
pub fn ephemeral() {
EphemeralCache(style.ephemeral())
}

@target(erlang)
pub fn persistent() -> Result(Cache, Nil) {
let assert Ok(subject) = actor.start(style.persistent(), state.loop)
Ok(Cache(subject))
Ok(PersistentCache(subject))
}

@target(erlang)
pub fn render(cache: Cache) -> String {
let Cache(subject) = cache
process.try_call(subject, state.Render, 1000)
|> result.nil_error()
|> result.unwrap("")
case cache {
EphemeralCache(cache:) -> style.render(cache)
PersistentCache(subject:) -> {
process.try_call(subject, state.Render, 1000)
|> result.nil_error()
|> result.unwrap("")
}
}
}

@target(erlang)
pub fn class_name(class: style.Class, cache: Cache) -> String {
let style.Class(string_representation: _, content: c) = class
let Cache(subject) = cache
use <- bool.guard(when: list.is_empty(c), return: "")
process.try_call(subject, state.Fetch(class, _), within: 100)
|> result.unwrap("")
pub fn class_name(class: style.Class, cache: Cache) -> #(Cache, String) {
case cache {
EphemeralCache(cache:) -> {
let #(cache, class_name) = style.class_name(class, cache)
#(EphemeralCache(cache:), class_name)
}
PersistentCache(subject:) -> {
let style.Class(string_representation: _, content: c) = class
use <- bool.guard(when: list.is_empty(c), return: #(cache, ""))
process.try_call(subject, state.Fetch(class, _), within: 100)
|> result.unwrap("")
|> pair.new(cache, _)
}
}
}
1 change: 0 additions & 1 deletion sketch/src/sketch/internals/cache/state.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import gleam/erlang/process.{type Subject}
import gleam/otp/actor
import gleam/string
import sketch/internals/style

pub type Request {
Expand Down
5 changes: 2 additions & 3 deletions sketch/src/sketch/internals/style.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import gleam/string
import sketch/internals/class
import sketch/internals/string as sketch_string

@external(erlang, "erlang", "phash2")
@external(javascript, "../../xxhash.ffi.mjs", "xxHash32")
fn xx_hash32(content: String) -> Int {
0
}
fn xx_hash32(content: String) -> Int

pub type Class {
Class(string_representation: String, content: List(Style))
Expand Down
2 changes: 1 addition & 1 deletion sketch_css/gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ repository = {type = "github", user = "ghivert", repo = "sketch"}
[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
glance = ">= 0.11.0 and < 1.0.0"
sketch = { path = "../sketch" }
simplifile = ">= 2.0.1 and < 3.0.0"
gleam_erlang = ">= 0.25.0 and < 1.0.0"
glint = ">= 1.0.1 and < 2.0.0"
argv = ">= 1.0.2 and < 2.0.0"
sketch = ">= 3.0.0 and < 4.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
4 changes: 2 additions & 2 deletions sketch_css/manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ packages = [
{ name = "glexer", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "BD477AD657C2B637FEF75F2405FAEFFA533F277A74EF1A5E17B55B1178C228FB" },
{ name = "glint", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "369C8A289017F73581D6B9FE2F5748169EB6FA021FFAA34FA7A49EE2094C73B3" },
{ name = "simplifile", version = "2.0.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "5FFEBD0CAB39BDD343C3E1CCA6438B2848847DC170BA2386DF9D7064F34DF000" },
{ name = "sketch", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], source = "local", path = "../sketch" },
{ name = "sketch", version = "3.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "sketch", source = "hex", outer_checksum = "6D851AECDB2DF4EFC4E93AA11AF91B7F443C044CA01AA47703B41A73730FC6C5" },
{ name = "snag", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "54D32E16E33655346AA3E66CBA7E191DE0A8793D2C05284E3EFB90AD2CE92BCC" },
{ name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" },
]
Expand All @@ -28,4 +28,4 @@ gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
glint = { version = ">= 1.0.1 and < 2.0.0" }
simplifile = { version = ">= 2.0.1 and < 3.0.0" }
sketch = { path = "../sketch" }
sketch = { version = ">= 3.0.0 and < 4.0.0" }
2 changes: 1 addition & 1 deletion sketch_lustre/gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ repository = {type = "github", user = "ghivert", repo = "sketch"}

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
sketch = { path = "../sketch" }
lustre = ">= 4.3.1 and < 5.0.0"
plinth = ">= 0.4.9 and < 1.0.0"
sketch = ">= 3.0.0 and < 4.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
6 changes: 3 additions & 3 deletions sketch_lustre/manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ packages = [
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
{ name = "lustre", version = "4.3.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "A0010C17CE8C847A2B979CE9FE68FB94AE9D75247D1173594C10B7C2FD3C83F2" },
{ name = "plinth", version = "0.4.9", build_tools = ["gleam"], requirements = ["conversation", "gleam_javascript", "gleam_json", "gleam_stdlib"], otp_app = "plinth", source = "hex", outer_checksum = "663C788C00FF908662B47B78B1CEBE1260AB814B45531AA42EBAEE974CDC7E27" },
{ name = "sketch", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], source = "local", path = "../sketch" },
{ name = "sketch", version = "3.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "sketch", source = "hex", outer_checksum = "6D851AECDB2DF4EFC4E93AA11AF91B7F443C044CA01AA47703B41A73730FC6C5" },
{ name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" },
]

[requirements]
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
lustre = { version = ">= 4.3.1 and < 5.0.0" }
plinth = { version = ">= 0.4.9 and < 1.0.0"}
sketch = { path = "../sketch" }
plinth = { version = ">= 0.4.9 and < 1.0.0" }
sketch = { version = ">= 3.0.0 and < 4.0.0" }
Loading