Sync feature
The sync feature uses Arc<RwLock<T>> in place of Rc<RefCell<T>> for Res and State. This allows state and resource objects to be thread safe. Added threading example Added threading documentation
This commit is contained in:
parent
3b099bfbb0
commit
5896818dbd
|
@ -10,5 +10,7 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Build
|
||||
- name: test base
|
||||
run: cargo test
|
||||
- name: test sync
|
||||
run: cargo test --features="sync"
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -42,7 +42,17 @@ path = "examples/navigation.rs"
|
|||
name = "stack"
|
||||
path = "examples/stack.rs"
|
||||
|
||||
[[example]]
|
||||
name = "external"
|
||||
path = "examples/threading.rs"
|
||||
required-features = ["sync"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.71"
|
||||
crossterm = "0.27"
|
||||
ctrlc = "3.3.1"
|
||||
|
||||
[features]
|
||||
sync = []
|
||||
default = []
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Project name
|
||||
name: Arkham CLI Library
|
||||
name: Arkham
|
||||
# The author displayed on the title page during PDF generation
|
||||
author: ~
|
||||
# The path relative to the project root where the compiled static site files
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
menu_position: 1
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
title: Threading
|
||||
subtitle: Guides
|
||||
---
|
||||
|
||||
|
||||
# The _sync_ flag
|
||||
|
||||
You can enable the sync flag in your _cargo.toml_ file by changing the Arkham deceleration to:
|
||||
|
||||
```Toml
|
||||
arkham = { version = "*", features=["sync"] }
|
||||
```
|
||||
|
||||
With the _sync_ flag enabled `Res` and `State` will be thread safe. This makes it easy to pass the application state or resources to other threads for processing.
|
||||
|
||||
# Render signals
|
||||
|
||||
When manipulating data from outside of components, especially in other threads, it is useful to be able to notify the app instance that it needs to render changes to the screen. a `Renderer` provides the ability to signal the app instance that it needs to render.
|
||||
|
||||
|
||||
```Rust
|
||||
let mut app = App::new(root_view);
|
||||
let renderer = app.get_renderer();
|
||||
std::thread::spawn(move || loop {
|
||||
renderer.render()
|
||||
std::thread::sleep(
|
||||
std::time::Duration::from_secs(10)
|
||||
);
|
||||
});
|
||||
app.run();
|
||||
|
||||
```
|
||||
|
||||
# Full threading example
|
||||
|
||||
```Rust
|
||||
use arkham::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AppState {
|
||||
pub counter: i32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app_state = State::new(AppState::default());
|
||||
let mut app = App::new(root_view)
|
||||
.bind_state(app_state.clone());
|
||||
let renderer = app.get_renderer();
|
||||
|
||||
std::thread::spawn(move || loop {
|
||||
app_state.get_mut().counter += 1;
|
||||
renderer.render();
|
||||
std::thread::sleep(
|
||||
std::time::Duration::from_secs(1)
|
||||
);
|
||||
});
|
||||
|
||||
app.run().unwrap();
|
||||
}
|
||||
|
||||
fn root_view(
|
||||
ctx: &mut ViewContext,
|
||||
state: State<AppState>
|
||||
) {
|
||||
ctx.insert(
|
||||
0,
|
||||
format!("Count is {}", state.get().counter)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
use arkham::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AppState {
|
||||
pub counter: i32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app_state = State::new(AppState::default());
|
||||
let mut app = App::new(root_view).bind_state(app_state.clone());
|
||||
let renderer = app.get_renderer();
|
||||
|
||||
std::thread::spawn(move || loop {
|
||||
app_state.get_mut().counter += 1;
|
||||
renderer.render();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
});
|
||||
|
||||
app.run().unwrap();
|
||||
}
|
||||
|
||||
fn root_view(ctx: &mut ViewContext, state: State<AppState>) {
|
||||
ctx.insert(0, format!("Count is {}", state.get().counter));
|
||||
}
|
40
src/app.rs
40
src/app.rs
|
@ -117,7 +117,14 @@ where
|
|||
/// Alternatively, App::insert_state can be used to insert a state object,
|
||||
/// that can be borrowed mutable.
|
||||
pub fn insert_resource<T: Any>(self, v: T) -> Self {
|
||||
self.container.borrow_mut().bind(Res::new(v));
|
||||
self.bind_resource(Res::new(v))
|
||||
}
|
||||
|
||||
/// Bind an existing resource to the application
|
||||
///
|
||||
/// Similar to `App::insert_resource` except it accepts an existing resource.
|
||||
pub fn bind_resource<T: Any>(self, v: Res<T>) -> Self {
|
||||
self.container.borrow_mut().bind(v);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -142,7 +149,14 @@ where
|
|||
/// }
|
||||
/// ````
|
||||
pub fn insert_state<T: Any>(self, v: T) -> Self {
|
||||
self.container.borrow_mut().bind(State::new(v));
|
||||
self.bind_state(State::new(v))
|
||||
}
|
||||
|
||||
/// Binds an existing state to the application.
|
||||
///
|
||||
/// Similar to `App::insert_state` but will accept an existing state
|
||||
pub fn bind_state<T: Any>(self, v: State<T>) -> Self {
|
||||
self.container.borrow_mut().bind(v);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -247,3 +261,25 @@ impl Terminal {
|
|||
crossterm::terminal::size().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[cfg(feature = "sync")]
|
||||
#[test]
|
||||
fn test_threaded_state() {
|
||||
use crate::prelude::{App, State, ViewContext};
|
||||
|
||||
#[derive(Default)]
|
||||
struct S {
|
||||
i: i32,
|
||||
}
|
||||
|
||||
let root_view = |_: &mut ViewContext| {};
|
||||
|
||||
let state = State::new(S::default());
|
||||
App::new(root_view).bind_state(state.clone());
|
||||
std::thread::spawn(move || {
|
||||
state.get_mut().i = 10;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
#[cfg(not(feature = "sync"))]
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
ops::Deref,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::context::ViewContext;
|
||||
|
@ -36,10 +40,19 @@ impl Container {
|
|||
|
||||
/// A wrapper for state objcets. This internally holds a reference counted
|
||||
/// poitner to the object and is used when injecting itno functions.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub struct State<T: ?Sized>(Rc<RefCell<T>>);
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub struct State<T: ?Sized>(Arc<RwLock<T>>);
|
||||
|
||||
impl<T> State<T> {
|
||||
/// Create a new state wrapper.
|
||||
#[cfg(feature = "sync")]
|
||||
pub fn new(val: T) -> Self {
|
||||
State(Arc::new(RwLock::new(val)))
|
||||
}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub fn new(val: T) -> Self {
|
||||
State(Rc::new(RefCell::new(val)))
|
||||
}
|
||||
|
@ -55,6 +68,11 @@ impl<T> State<T> {
|
|||
/// state.get_mut().0 = 6;
|
||||
/// assert_eq!(state.get().0, 6);
|
||||
/// ```
|
||||
#[cfg(feature = "sync")]
|
||||
pub fn get_mut(&self) -> std::sync::RwLockWriteGuard<T> {
|
||||
self.0.write().unwrap()
|
||||
}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub fn get_mut(&self) -> std::cell::RefMut<T> {
|
||||
RefCell::borrow_mut(&self.0)
|
||||
}
|
||||
|
@ -68,6 +86,11 @@ impl<T> State<T> {
|
|||
/// let state = State::new(MyState(4));
|
||||
/// assert_eq!(state.get().0, 4);
|
||||
/// ```
|
||||
#[cfg(feature = "sync")]
|
||||
pub fn get(&self) -> std::sync::RwLockReadGuard<T> {
|
||||
self.0.read().unwrap()
|
||||
}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub fn get(&self) -> std::cell::Ref<T> {
|
||||
RefCell::borrow(&self.0)
|
||||
}
|
||||
|
@ -88,10 +111,20 @@ impl<T: ?Sized + 'static> FromContainer for State<T> {
|
|||
/// A wrapper for resources stored within the app. This wrapper is returned
|
||||
/// when objects are injected into component functions and provide immutable
|
||||
/// access
|
||||
#[cfg(feature = "sync")]
|
||||
#[derive(Debug)]
|
||||
pub struct Res<T: ?Sized>(Arc<T>);
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
#[derive(Debug)]
|
||||
pub struct Res<T: ?Sized>(Rc<T>);
|
||||
|
||||
impl<T> Res<T> {
|
||||
#[cfg(feature = "sync")]
|
||||
pub fn new(val: T) -> Self {
|
||||
Res(Arc::new(val))
|
||||
}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub fn new(val: T) -> Self {
|
||||
Res(Rc::new(val))
|
||||
}
|
||||
|
@ -109,6 +142,16 @@ impl<T: ?Sized> Clone for Res<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
impl<T: ?Sized> Deref for Res<T> {
|
||||
type Target = Arc<T>;
|
||||
|
||||
fn deref(&self) -> &Arc<T> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<T: ?Sized> Deref for Res<T> {
|
||||
type Target = Rc<T>;
|
||||
|
||||
|
|
Loading…
Reference in New Issue