Skip to content

Commit

Permalink
Merge pull request #24 from ghivert/fix/add-ephemeral-caches-on-beam
Browse files Browse the repository at this point in the history
Add ephemeral caches on BEAM
  • Loading branch information
ghivert authored Aug 9, 2024
2 parents f441594 + 3da1ead commit e341f11
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 52 deletions.
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

0 comments on commit e341f11

Please sign in to comment.