Skip to content

Instantly share code, notes, and snippets.

@namse
Created March 27, 2023 09:00
Show Gist options
  • Save namse/0ac527707b8a504049daf2541d1cf6de to your computer and use it in GitHub Desktop.
Save namse/0ac527707b8a504049daf2541d1cf6de to your computer and use it in GitHub Desktop.
redux-react-like rust rendering system
use std::sync::{Arc, Mutex};
fn main() {
println!("Hello, world!");
let app_state = TodoAppState {
text_input: text_input::State::new(),
todos: TodoState { todos: vec![] },
visibility_filter: VisibilityFilterState {
visibility_filter: VisibilityFilter::ShowAll,
},
};
run(app_state, |app_state| {
let TodoAppState {
todos,
visibility_filter,
text_input,
} = app_state;
TodoAppProps {
todos: todos.todos.clone(),
visibility_filter: visibility_filter.visibility_filter,
text_input: text_input.to_props(),
}
})
}
fn run<State: Reduce, Props: Render>(mut state: State, to_props: impl Fn(&State) -> Props) {
loop {
let event = get_event();
state = state.reduce(&event);
let props = to_props(&state);
let element = props.render();
draw_element(element);
}
}
fn get_event() -> Box<dyn std::any::Any> {
todo!()
}
fn draw_element(element: Element) {
todo!()
}
trait Reduce {
fn reduce(self, event: &dyn std::any::Any) -> Self;
}
#[derive(Clone, Copy, PartialEq)]
enum VisibilityFilter {
ShowAll,
ShowCompleted,
}
struct VisibilityFilterState {
visibility_filter: VisibilityFilter,
}
enum VisibilityFilterEvent {
SetVisibilityFilter(VisibilityFilter),
}
impl Reduce for VisibilityFilterState {
fn reduce(self, event: &dyn std::any::Any) -> Self {
if let Some(event) = event.downcast_ref::<VisibilityFilterEvent>() {
match event {
VisibilityFilterEvent::SetVisibilityFilter(visibility_filter) => {
VisibilityFilterState {
visibility_filter: *visibility_filter,
}
}
}
} else {
self
}
}
}
#[derive(PartialEq, Clone)]
struct Todo {
text: String,
completed: bool,
}
struct TodoState {
todos: Vec<Todo>,
}
enum TodoEvent {
AddTodo { text: String },
ToggleTodo { index: usize },
}
impl Reduce for TodoState {
fn reduce(mut self, event: &dyn std::any::Any) -> Self {
if let Some(event) = event.downcast_ref::<TodoEvent>() {
match event {
TodoEvent::AddTodo { text } => {
self.todos.push(Todo {
text: text.clone(),
completed: false,
});
}
TodoEvent::ToggleTodo { index } => {
self.todos[*index].completed = !self.todos[*index].completed;
}
}
}
self
}
}
mod text_input {
use super::*;
pub struct State {}
impl State {
pub fn new() -> Self {
Self {}
}
pub fn to_props(&self) -> Props {
Props {}
}
}
pub enum Event {}
impl super::Reduce for State {
fn reduce(self, event: &dyn std::any::Any) -> Self {
if let Some(event) = event.downcast_ref::<Event>() {
todo!()
} else {
self
}
}
}
#[derive(PartialEq, Clone)]
pub struct Props {}
impl Render for Props {
fn render(&self) -> Element {
todo!()
}
fn eq(&self, other: &dyn std::any::Any) -> bool {
if let Some(other) = other.downcast_ref::<Self>() {
self == other
} else {
false
}
}
}
}
struct TodoAppState {
todos: TodoState,
visibility_filter: VisibilityFilterState,
text_input: text_input::State,
}
impl Reduce for TodoAppState {
fn reduce(self, event: &dyn std::any::Any) -> Self {
TodoAppState {
todos: self.todos.reduce(event),
visibility_filter: self.visibility_filter.reduce(event),
text_input: self.text_input.reduce(event),
}
}
}
#[derive(Clone)]
struct Element {}
trait Render {
fn render(&self) -> Element;
fn eq(&self, other: &dyn std::any::Any) -> bool;
}
fn div(children: impl IntoIterator<Item = View>) -> Element {
todo!()
}
thread_local! {
static VIEW_CACHE: Mutex<Vec<View>> = Mutex::new(Vec::new());
}
fn view(render: impl Render + 'static) -> View {
VIEW_CACHE.with(move |cache| {
let mut cache = cache.lock().unwrap();
match cache.iter().find(|cached| cached.render.eq(&render)) {
Some(cached) => {
let view = cached.clone();
view
}
None => {
let element = render.render();
let view = View {
render: Arc::new(render),
element,
};
cache.push(view.clone());
view
}
}
})
}
#[derive(Clone)]
struct View {
render: Arc<dyn Render>,
element: Element,
}
#[derive(PartialEq)]
struct TodoAppProps {
todos: Vec<Todo>,
visibility_filter: VisibilityFilter,
text_input: text_input::Props,
}
impl Render for TodoAppProps {
fn render(&self) -> Element {
let filtered_todos = self
.todos
.iter()
.filter(|todo| match self.visibility_filter {
VisibilityFilter::ShowAll => true,
VisibilityFilter::ShowCompleted => todo.completed,
})
.cloned()
.collect::<Vec<_>>();
div([
// view 없이 한번에 넣는 방법은? 좀 더 깔끔하게 할 수 있을까?
view(TodoListProps {
todos: filtered_todos,
}),
view(VisibilityFilterProps {
visibility_filter: self.visibility_filter,
}),
view(self.text_input.clone()),
])
}
fn eq(&self, other: &dyn std::any::Any) -> bool {
if let Some(other) = other.downcast_ref::<Self>() {
self == other
} else {
false
}
}
}
#[derive(PartialEq)]
struct TodoListProps {
todos: Vec<Todo>,
}
impl Render for TodoListProps {
fn render(&self) -> Element {
let mut elements = vec![];
for (index, todo) in self.todos.iter().enumerate() {
elements.push(view(TodoProps {
text: todo.text.clone(),
completed: todo.completed,
index,
}));
}
div(elements)
}
fn eq(&self, other: &dyn std::any::Any) -> bool {
if let Some(other) = other.downcast_ref::<Self>() {
self == other
} else {
false
}
}
}
#[derive(PartialEq)]
struct TodoProps {
text: String,
completed: bool,
index: usize,
}
impl Render for TodoProps {
fn render(&self) -> Element {
text(&self.text).event(|build| {
let index = self.index;
build.on_click_fn(move |_| Some(TodoEvent::ToggleTodo { index }));
build.on_click(TodoEvent::ToggleTodo { index: self.index });
})
}
fn eq(&self, other: &dyn std::any::Any) -> bool {
if let Some(other) = other.downcast_ref::<Self>() {
self == other
} else {
false
}
}
}
fn text(text: impl ToString) -> Element {
todo!()
}
impl Element {
fn event(self, build: impl FnOnce(&mut EventBuilder)) -> Element {
todo!()
}
}
struct EventBuilder {}
impl EventBuilder {
fn on_click_fn<Event: std::any::Any>(
&mut self,
handler: impl Fn(ClickEvent) -> Option<Event> + 'static,
) -> Self {
todo!()
}
fn on_click<Event: std::any::Any>(&mut self, event: Event) -> Self {
todo!()
}
}
struct ClickEvent {}
#[derive(PartialEq)]
struct VisibilityFilterProps {
visibility_filter: VisibilityFilter,
}
impl Render for VisibilityFilterProps {
fn render(&self) -> Element {
todo!()
}
fn eq(&self, other: &dyn std::any::Any) -> bool {
if let Some(other) = other.downcast_ref::<Self>() {
self == other
} else {
false
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment