mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-14 16:37:14 +00:00
Merge pull request #261 from DioxusLabs/jk/fermi
feat: integrate fermi into the main repo
This commit is contained in:
commit
e5d0b9e3d9
26 changed files with 658 additions and 48 deletions
9
.github/workflows/macos.yml
vendored
9
.github/workflows/macos.yml
vendored
|
@ -1,7 +1,10 @@
|
|||
name: macOS tests
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- packages/**
|
||||
- examples/**
|
||||
|
@ -9,10 +12,6 @@ on:
|
|||
- .github/**
|
||||
- lib.rs
|
||||
- Cargo.toml
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
47
.github/workflows/main.yml
vendored
47
.github/workflows/main.yml
vendored
|
@ -1,7 +1,10 @@
|
|||
name: Rust CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- packages/**
|
||||
- examples/**
|
||||
|
@ -9,10 +12,6 @@ on:
|
|||
- .github/**
|
||||
- lib.rs
|
||||
- Cargo.toml
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
check:
|
||||
|
@ -90,21 +89,23 @@ jobs:
|
|||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
coverage:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: xd009642/tarpaulin:develop-nightly
|
||||
options: --security-opt seccomp=unconfined
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Generate code coverage
|
||||
run: |
|
||||
apt-get update &&\
|
||||
apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
|
||||
cargo +nightly tarpaulin --verbose --tests --all-features --workspace --timeout 120 --out Xml
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
fail_ci_if_error: false
|
||||
# Coverage is disabled until we can fix it
|
||||
# coverage:
|
||||
# name: Coverage
|
||||
# runs-on: ubuntu-latest
|
||||
# container:
|
||||
# image: xd009642/tarpaulin:develop-nightly
|
||||
# options: --security-opt seccomp=unconfined
|
||||
# steps:
|
||||
# - name: Checkout repository
|
||||
# uses: actions/checkout@v2
|
||||
# - name: Generate code coverage
|
||||
# run: |
|
||||
# apt-get update &&\
|
||||
# apt-get install build-essential &&\
|
||||
# apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
|
||||
# cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml
|
||||
# - name: Upload to codecov.io
|
||||
# uses: codecov/codecov-action@v2
|
||||
# with:
|
||||
# fail_ci_if_error: false
|
||||
|
|
9
.github/workflows/windows.yml
vendored
9
.github/workflows/windows.yml
vendored
|
@ -1,7 +1,10 @@
|
|||
name: windows
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- packages/**
|
||||
- examples/**
|
||||
|
@ -9,10 +12,6 @@ on:
|
|||
- .github/**
|
||||
- lib.rs
|
||||
- Cargo.toml
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
18
Cargo.toml
18
Cargo.toml
|
@ -15,6 +15,7 @@ dioxus-core = { path = "./packages/core", version = "^0.1.9" }
|
|||
dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
|
||||
dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
|
||||
dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true }
|
||||
fermi = { path = "./packages/fermi", version = "^0.1.0", optional = true }
|
||||
|
||||
dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true }
|
||||
dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true }
|
||||
|
@ -36,20 +37,6 @@ web = ["dioxus-web"]
|
|||
desktop = ["dioxus-desktop"]
|
||||
router = ["dioxus-router"]
|
||||
|
||||
devtool = ["dioxus-desktop/devtool"]
|
||||
fullscreen = ["dioxus-desktop/fullscreen"]
|
||||
transparent = ["dioxus-desktop/transparent"]
|
||||
|
||||
tray = ["dioxus-desktop/tray"]
|
||||
ayatana = ["dioxus-desktop/ayatana"]
|
||||
|
||||
# "dioxus-router/web"
|
||||
# "dioxus-router/desktop"
|
||||
# desktop = ["dioxus-desktop", "dioxus-router/desktop"]
|
||||
# mobile = ["dioxus-mobile"]
|
||||
# liveview = ["dioxus-liveview"]
|
||||
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"packages/core",
|
||||
|
@ -61,6 +48,7 @@ members = [
|
|||
"packages/desktop",
|
||||
"packages/mobile",
|
||||
"packages/interpreter",
|
||||
"packages/fermi",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -75,4 +63,4 @@ serde_json = "1.0.79"
|
|||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
reqwest = { version = "0.11.9", features = ["json"] }
|
||||
dioxus = { path = ".", features = ["desktop", "ssr", "router"] }
|
||||
dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi"] }
|
||||
|
|
1
docs/fermi/.gitignore
vendored
Normal file
1
docs/fermi/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
book
|
6
docs/fermi/book.toml
Normal file
6
docs/fermi/book.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[book]
|
||||
authors = ["Jonathan Kelley"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Fermi Guide"
|
3
docs/fermi/src/SUMMARY.md
Normal file
3
docs/fermi/src/SUMMARY.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Summary
|
||||
|
||||
- [Chapter 1](./chapter_1.md)
|
1
docs/fermi/src/chapter_1.md
Normal file
1
docs/fermi/src/chapter_1.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Chapter 1
|
30
examples/fermi.rs
Normal file
30
examples/fermi.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use fermi::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(app)
|
||||
}
|
||||
|
||||
static NAME: Atom<String> = |_| "world".to_string();
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let name = use_read(&cx, NAME);
|
||||
|
||||
cx.render(rsx! {
|
||||
div { "hello {name}!" }
|
||||
Child {}
|
||||
})
|
||||
}
|
||||
|
||||
fn Child(cx: Scope) -> Element {
|
||||
let set_name = use_set(&cx, NAME);
|
||||
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
onclick: move |_| set_name("dioxus".to_string()),
|
||||
"reset name"
|
||||
}
|
||||
})
|
||||
}
|
14
packages/fermi/Cargo.toml
Normal file
14
packages/fermi/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "fermi"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core" }
|
||||
im-rc = { version = "15.0.0", features = ["serde"] }
|
||||
log = "0.4.14"
|
||||
|
||||
[dev-dependencies]
|
||||
closure = "0.3.0"
|
92
packages/fermi/README.md
Normal file
92
packages/fermi/README.md
Normal file
|
@ -0,0 +1,92 @@
|
|||
|
||||
<div align="center">
|
||||
<h1>Fermi ⚛</h1>
|
||||
<p>
|
||||
<strong>Atom-based global state management solution for Dioxus</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div align="center">
|
||||
<!-- Crates version -->
|
||||
<a href="https://crates.io/crates/dioxus">
|
||||
<img src="https://img.shields.io/crates/v/dioxus.svg?style=flat-square"
|
||||
alt="Crates.io version" />
|
||||
</a>
|
||||
<!-- Downloads -->
|
||||
<a href="https://crates.io/crates/dioxus">
|
||||
<img src="https://img.shields.io/crates/d/dioxus.svg?style=flat-square"
|
||||
alt="Download" />
|
||||
</a>
|
||||
<!-- docs -->
|
||||
<a href="https://docs.rs/dioxus">
|
||||
<img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
|
||||
alt="docs.rs docs" />
|
||||
</a>
|
||||
<!-- CI -->
|
||||
<a href="https://github.com/jkelleyrtp/dioxus/actions">
|
||||
<img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
|
||||
alt="CI status" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
-----
|
||||
|
||||
Fermi is a global state management solution for Dioxus that's as easy as `use_state`.
|
||||
|
||||
Inspired by atom-based state management solutions, all state in Fermi starts as an `atom`:
|
||||
|
||||
```rust
|
||||
static NAME: Atom<&str> = |_| "Dioxus";
|
||||
```
|
||||
|
||||
From anywhere in our app, we can read our the value of our atom:
|
||||
|
||||
```rust
|
||||
fn NameCard(cx: Scope) -> Element {
|
||||
let name = use_read(&cx, NAME);
|
||||
cx.render(rsx!{ h1 { "Hello, {name}"} })
|
||||
}
|
||||
```
|
||||
|
||||
We can also set the value of our atom, also from anywhere in our app:
|
||||
|
||||
```rust
|
||||
fn NameCard(cx: Scope) -> Element {
|
||||
let set_name = use_set(&cx, NAME);
|
||||
cx.render(rsx!{
|
||||
button {
|
||||
onclick: move |_| set_name("Fermi"),
|
||||
"Set name to fermi"
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
It's that simple!
|
||||
|
||||
## Installation
|
||||
Fermi is currently under construction, so you have to use the `master` branch to get started.
|
||||
|
||||
```rust
|
||||
[depdencies]
|
||||
fermi = { git = "https://github.com/dioxuslabs/fermi" }
|
||||
```
|
||||
|
||||
|
||||
## Running examples
|
||||
|
||||
The examples here use Dioxus Desktop to showcase their functionality. To run an example, use
|
||||
```
|
||||
$ cargo run --example EXAMPLE
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
Broadly our feature set to required to be released includes:
|
||||
- [x] Support for Atoms
|
||||
- [x] Support for AtomRef (for values that aren't clone)
|
||||
- [ ] Support for Atom Families
|
||||
- [ ] Support for memoized Selectors
|
||||
- [ ] Support for memoized SelectorFamilies
|
||||
- [ ] Support for UseFermiCallback for access to fermi from async
|
28
packages/fermi/src/atoms/atom.rs
Normal file
28
packages/fermi/src/atoms/atom.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use crate::{AtomId, AtomRoot, Readable, Writable};
|
||||
|
||||
pub type Atom<T> = fn(AtomBuilder) -> T;
|
||||
pub struct AtomBuilder;
|
||||
|
||||
impl<V> Readable<V> for Atom<V> {
|
||||
fn read(&self, _root: AtomRoot) -> Option<V> {
|
||||
todo!()
|
||||
}
|
||||
fn init(&self) -> V {
|
||||
(*self)(AtomBuilder)
|
||||
}
|
||||
fn unique_id(&self) -> AtomId {
|
||||
*self as *const ()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Writable<V> for Atom<V> {
|
||||
fn write(&self, _root: AtomRoot, _value: V) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atom_compiles() {
|
||||
static TEST_ATOM: Atom<&str> = |_| "hello";
|
||||
dbg!(TEST_ATOM.init());
|
||||
}
|
25
packages/fermi/src/atoms/atomfamily.rs
Normal file
25
packages/fermi/src/atoms/atomfamily.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use crate::{AtomId, AtomRoot, Readable, Writable};
|
||||
use im_rc::HashMap as ImMap;
|
||||
|
||||
pub struct AtomFamilyBuilder;
|
||||
pub type AtomFamily<K, V> = fn(AtomFamilyBuilder) -> ImMap<K, V>;
|
||||
|
||||
impl<K, V> Readable<ImMap<K, V>> for AtomFamily<K, V> {
|
||||
fn read(&self, _root: AtomRoot) -> Option<ImMap<K, V>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn init(&self) -> ImMap<K, V> {
|
||||
(*self)(AtomFamilyBuilder)
|
||||
}
|
||||
|
||||
fn unique_id(&self) -> AtomId {
|
||||
*self as *const ()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Writable<ImMap<K, V>> for AtomFamily<K, V> {
|
||||
fn write(&self, _root: AtomRoot, _value: ImMap<K, V>) {
|
||||
todo!()
|
||||
}
|
||||
}
|
25
packages/fermi/src/atoms/atomref.rs
Normal file
25
packages/fermi/src/atoms/atomref.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use crate::{AtomId, AtomRoot, Readable};
|
||||
use std::cell::RefCell;
|
||||
|
||||
pub struct AtomRefBuilder;
|
||||
pub type AtomRef<T> = fn(AtomRefBuilder) -> T;
|
||||
|
||||
impl<V> Readable<RefCell<V>> for AtomRef<V> {
|
||||
fn read(&self, _root: AtomRoot) -> Option<RefCell<V>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn init(&self) -> RefCell<V> {
|
||||
RefCell::new((*self)(AtomRefBuilder))
|
||||
}
|
||||
|
||||
fn unique_id(&self) -> AtomId {
|
||||
*self as *const ()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atom_compiles() {
|
||||
static TEST_ATOM: AtomRef<Vec<String>> = |_| vec![];
|
||||
dbg!(TEST_ATOM.init());
|
||||
}
|
1
packages/fermi/src/atoms/selector.rs
Normal file
1
packages/fermi/src/atoms/selector.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
1
packages/fermi/src/atoms/selectorfamily.rs
Normal file
1
packages/fermi/src/atoms/selectorfamily.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
53
packages/fermi/src/callback.rs
Normal file
53
packages/fermi/src/callback.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
#![allow(clippy::all, unused)]
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
use crate::{AtomRoot, Readable, Writable};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CallbackApi {
|
||||
root: Rc<AtomRoot>,
|
||||
}
|
||||
|
||||
impl CallbackApi {
|
||||
// get the current value of the atom
|
||||
pub fn get<V>(&self, atom: impl Readable<V>) -> &V {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// get the current value of the atom in its RC container
|
||||
pub fn get_rc<V>(&self, atom: impl Readable<V>) -> &Rc<V> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// set the current value of the atom
|
||||
pub fn set<V>(&self, atom: impl Writable<V>, value: V) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_atom_context(cx: &ScopeState) -> &CallbackApi {
|
||||
todo!()
|
||||
}
|
||||
|
||||
macro_rules! use_callback {
|
||||
(&$cx:ident, [$($cap:ident),*], move || $body:expr) => {
|
||||
move || {
|
||||
$(
|
||||
#[allow(unused_mut)]
|
||||
let mut $cap = $cap.to_owned();
|
||||
)*
|
||||
$cx.spawn($body);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! to_owned {
|
||||
($($es:ident),+) => {$(
|
||||
#[allow(unused_mut)]
|
||||
let mut $es = $es.to_owned();
|
||||
)*}
|
||||
}
|
62
packages/fermi/src/hooks/atom_ref.rs
Normal file
62
packages/fermi/src/hooks/atom_ref.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use crate::{use_atom_root, AtomId, AtomRef, AtomRoot, Readable};
|
||||
use dioxus_core::{ScopeId, ScopeState};
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
pub fn use_atom_ref<T: 'static>(cx: &ScopeState, atom: AtomRef<T>) -> &UseAtomRef<T> {
|
||||
let root = use_atom_root(cx);
|
||||
|
||||
cx.use_hook(|_| {
|
||||
root.initialize(atom);
|
||||
UseAtomRef {
|
||||
ptr: atom.unique_id(),
|
||||
root: root.clone(),
|
||||
scope_id: cx.scope_id(),
|
||||
value: root.register(atom, cx.scope_id()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub struct UseAtomRef<T> {
|
||||
ptr: AtomId,
|
||||
value: Rc<RefCell<T>>,
|
||||
root: Rc<AtomRoot>,
|
||||
scope_id: ScopeId,
|
||||
}
|
||||
|
||||
impl<T: 'static> UseAtomRef<T> {
|
||||
pub fn read(&self) -> Ref<T> {
|
||||
self.value.borrow()
|
||||
}
|
||||
|
||||
pub fn write(&self) -> RefMut<T> {
|
||||
self.root.force_update(self.ptr);
|
||||
self.value.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn write_silent(&self) -> RefMut<T> {
|
||||
self.root.force_update(self.ptr);
|
||||
self.value.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn set(&self, new: T) {
|
||||
self.root.force_update(self.ptr);
|
||||
self.root.set(self.ptr, new);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for UseAtomRef<T> {
|
||||
fn drop(&mut self) {
|
||||
self.root.unsubscribe(self.ptr, self.scope_id)
|
||||
}
|
||||
}
|
11
packages/fermi/src/hooks/atom_root.rs
Normal file
11
packages/fermi/src/hooks/atom_root.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use crate::AtomRoot;
|
||||
use dioxus_core::ScopeState;
|
||||
use std::rc::Rc;
|
||||
|
||||
// Returns the atom root, initiaizing it at the root of the app if it does not exist.
|
||||
pub fn use_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {
|
||||
cx.use_hook(|_| match cx.consume_context::<AtomRoot>() {
|
||||
Some(root) => root,
|
||||
None => cx.provide_root_context(AtomRoot::new(cx.schedule_update_any())),
|
||||
})
|
||||
}
|
11
packages/fermi/src/hooks/init_atom_root.rs
Normal file
11
packages/fermi/src/hooks/init_atom_root.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use crate::AtomRoot;
|
||||
use dioxus_core::ScopeState;
|
||||
use std::rc::Rc;
|
||||
|
||||
// Initializes the atom root and retuns it;
|
||||
pub fn use_init_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {
|
||||
cx.use_hook(|_| match cx.consume_context::<AtomRoot>() {
|
||||
Some(ctx) => ctx,
|
||||
None => cx.provide_context(AtomRoot::new(cx.schedule_update_any())),
|
||||
})
|
||||
}
|
36
packages/fermi/src/hooks/read.rs
Normal file
36
packages/fermi/src/hooks/read.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use crate::{use_atom_root, AtomId, AtomRoot, Readable};
|
||||
use dioxus_core::{ScopeId, ScopeState};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn use_read<'a, V: 'static>(cx: &'a ScopeState, f: impl Readable<V>) -> &'a V {
|
||||
use_read_rc(cx, f).as_ref()
|
||||
}
|
||||
|
||||
pub fn use_read_rc<'a, V: 'static>(cx: &'a ScopeState, f: impl Readable<V>) -> &'a Rc<V> {
|
||||
let root = use_atom_root(cx);
|
||||
|
||||
struct UseReadInner<V> {
|
||||
root: Rc<AtomRoot>,
|
||||
id: AtomId,
|
||||
scope_id: ScopeId,
|
||||
value: Option<Rc<V>>,
|
||||
}
|
||||
|
||||
impl<V> Drop for UseReadInner<V> {
|
||||
fn drop(&mut self) {
|
||||
self.root.unsubscribe(self.id, self.scope_id)
|
||||
}
|
||||
}
|
||||
|
||||
let inner = cx.use_hook(|_| UseReadInner {
|
||||
value: None,
|
||||
root: root.clone(),
|
||||
scope_id: cx.scope_id(),
|
||||
id: f.unique_id(),
|
||||
});
|
||||
|
||||
let value = inner.root.register(f, cx.scope_id());
|
||||
|
||||
inner.value = Some(value);
|
||||
inner.value.as_ref().unwrap()
|
||||
}
|
13
packages/fermi/src/hooks/set.rs
Normal file
13
packages/fermi/src/hooks/set.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use crate::{use_atom_root, Writable};
|
||||
use dioxus_core::ScopeState;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn use_set<'a, T: 'static>(cx: &'a ScopeState, f: impl Writable<T>) -> &'a Rc<dyn Fn(T)> {
|
||||
let root = use_atom_root(cx);
|
||||
cx.use_hook(|_| {
|
||||
let id = f.unique_id();
|
||||
let root = root.clone();
|
||||
root.initialize(f);
|
||||
Rc::new(move |new| root.set(id, new)) as Rc<dyn Fn(T)>
|
||||
})
|
||||
}
|
58
packages/fermi/src/lib.rs
Normal file
58
packages/fermi/src/lib.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::*;
|
||||
}
|
||||
|
||||
mod callback;
|
||||
mod root;
|
||||
|
||||
pub use atoms::*;
|
||||
pub use callback::*;
|
||||
pub use hooks::*;
|
||||
pub use root::*;
|
||||
|
||||
mod atoms {
|
||||
mod atom;
|
||||
mod atomfamily;
|
||||
mod atomref;
|
||||
mod selector;
|
||||
mod selectorfamily;
|
||||
|
||||
pub use atom::*;
|
||||
pub use atomfamily::*;
|
||||
pub use atomref::*;
|
||||
pub use selector::*;
|
||||
pub use selectorfamily::*;
|
||||
}
|
||||
|
||||
pub mod hooks {
|
||||
mod atom_ref;
|
||||
mod atom_root;
|
||||
mod init_atom_root;
|
||||
mod read;
|
||||
mod set;
|
||||
pub use atom_ref::*;
|
||||
pub use atom_root::*;
|
||||
pub use init_atom_root::*;
|
||||
pub use read::*;
|
||||
pub use set::*;
|
||||
}
|
||||
|
||||
/// All Atoms are `Readable` - they support reading their value.
|
||||
///
|
||||
/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors.
|
||||
/// It is not very useful for your own code, but could be used to build new Atom primitives.
|
||||
pub trait Readable<V> {
|
||||
fn read(&self, root: AtomRoot) -> Option<V>;
|
||||
fn init(&self) -> V;
|
||||
fn unique_id(&self) -> AtomId;
|
||||
}
|
||||
|
||||
/// All Atoms are `Writable` - they support writing their value.
|
||||
///
|
||||
/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors.
|
||||
/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors
|
||||
pub trait Writable<V>: Readable<V> {
|
||||
fn write(&self, root: AtomRoot, value: V);
|
||||
}
|
103
packages/fermi/src/root.rs
Normal file
103
packages/fermi/src/root.rs
Normal file
|
@ -0,0 +1,103 @@
|
|||
use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use dioxus_core::ScopeId;
|
||||
use im_rc::HashSet;
|
||||
|
||||
use crate::Readable;
|
||||
|
||||
pub type AtomId = *const ();
|
||||
|
||||
pub struct AtomRoot {
|
||||
pub atoms: RefCell<HashMap<AtomId, Slot>>,
|
||||
pub update_any: Rc<dyn Fn(ScopeId)>,
|
||||
}
|
||||
|
||||
pub struct Slot {
|
||||
pub value: Rc<dyn Any>,
|
||||
pub subscribers: HashSet<ScopeId>,
|
||||
}
|
||||
|
||||
impl AtomRoot {
|
||||
pub fn new(update_any: Rc<dyn Fn(ScopeId)>) -> Self {
|
||||
Self {
|
||||
update_any,
|
||||
atoms: RefCell::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize<V: 'static>(&self, f: impl Readable<V>) {
|
||||
let id = f.unique_id();
|
||||
if self.atoms.borrow().get(&id).is_none() {
|
||||
self.atoms.borrow_mut().insert(
|
||||
id,
|
||||
Slot {
|
||||
value: Rc::new(f.init()),
|
||||
subscribers: HashSet::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register<V: 'static>(&self, f: impl Readable<V>, scope: ScopeId) -> Rc<V> {
|
||||
log::trace!("registering atom {:?}", f.unique_id());
|
||||
|
||||
let mut atoms = self.atoms.borrow_mut();
|
||||
|
||||
// initialize the value if it's not already initialized
|
||||
if let Some(slot) = atoms.get_mut(&f.unique_id()) {
|
||||
slot.subscribers.insert(scope);
|
||||
slot.value.clone().downcast().unwrap()
|
||||
} else {
|
||||
let value = Rc::new(f.init());
|
||||
let mut subscribers = HashSet::new();
|
||||
subscribers.insert(scope);
|
||||
|
||||
atoms.insert(
|
||||
f.unique_id(),
|
||||
Slot {
|
||||
value: value.clone(),
|
||||
subscribers,
|
||||
},
|
||||
);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set<V: 'static>(&self, ptr: AtomId, value: V) {
|
||||
let mut atoms = self.atoms.borrow_mut();
|
||||
|
||||
if let Some(slot) = atoms.get_mut(&ptr) {
|
||||
slot.value = Rc::new(value);
|
||||
log::trace!("found item with subscribers {:?}", slot.subscribers);
|
||||
|
||||
for scope in &slot.subscribers {
|
||||
log::trace!("updating subcsriber");
|
||||
(self.update_any)(*scope);
|
||||
}
|
||||
} else {
|
||||
log::trace!("no atoms found for {:?}", ptr);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsubscribe(&self, ptr: AtomId, scope: ScopeId) {
|
||||
let mut atoms = self.atoms.borrow_mut();
|
||||
|
||||
if let Some(slot) = atoms.get_mut(&ptr) {
|
||||
slot.subscribers.remove(&scope);
|
||||
}
|
||||
}
|
||||
|
||||
// force update of all subscribers
|
||||
pub fn force_update(&self, ptr: AtomId) {
|
||||
if let Some(slot) = self.atoms.borrow_mut().get(&ptr) {
|
||||
for scope in slot.subscribers.iter() {
|
||||
log::trace!("updating subcsriber");
|
||||
(self.update_any)(*scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read<V>(&self, _f: impl Readable<V>) -> &V {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -340,6 +340,9 @@ pub use dioxus_web as web;
|
|||
#[cfg(feature = "desktop")]
|
||||
pub use dioxus_desktop as desktop;
|
||||
|
||||
#[cfg(feature = "fermi")]
|
||||
pub use fermi;
|
||||
|
||||
// #[cfg(feature = "mobile")]
|
||||
// pub use dioxus_mobile as mobile;
|
||||
|
||||
|
|
46
tests/fermi.rs
Normal file
46
tests/fermi.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use fermi::*;
|
||||
|
||||
#[test]
|
||||
fn test_fermi() {
|
||||
let mut app = VirtualDom::new(App);
|
||||
app.rebuild();
|
||||
}
|
||||
|
||||
static TITLE: Atom<String> = |_| "".to_string();
|
||||
static USERS: AtomFamily<u32, String> = |_| Default::default();
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
Leaf { id: 0 }
|
||||
Leaf { id: 1 }
|
||||
Leaf { id: 2 }
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Props)]
|
||||
struct LeafProps {
|
||||
id: u32,
|
||||
}
|
||||
|
||||
fn Leaf(cx: Scope<LeafProps>) -> Element {
|
||||
let _user = use_read(&cx, TITLE);
|
||||
let _user = use_read(&cx, USERS);
|
||||
|
||||
rsx!(cx, div {
|
||||
button {
|
||||
onclick: move |_| {},
|
||||
"Start"
|
||||
}
|
||||
button {
|
||||
onclick: move |_| {},
|
||||
"Stop"
|
||||
}
|
||||
button {
|
||||
onclick: move |_| {},
|
||||
"Reverse"
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue