use crate::dom::program::app_context::WeakContext;
#[cfg(feature = "with-raf")]
use crate::dom::request_animation_frame;
#[cfg(feature = "with-ric")]
use crate::dom::request_idle_callback;
use crate::dom::DomNode;
use crate::dom::SkipDiff;
use crate::dom::SkipPath;
use crate::dom::{document, now, IdleDeadline, Measurements};
use crate::dom::{util::body, AnimationFrameHandle, Application, DomPatch, IdleCallbackHandle};
use crate::html::{self, attributes::class, text};
use crate::vdom;
use crate::vdom::diff;
use crate::vdom::diff_recursive;
use crate::vdom::Patch;
use std::collections::hash_map::DefaultHasher;
use std::collections::VecDeque;
use std::hash::{Hash, Hasher};
use std::mem::ManuallyDrop;
use std::{
any::TypeId,
cell::{Ref, RefCell, RefMut},
rc::Rc,
rc::Weak,
};
use wasm_bindgen::{JsCast, JsValue};
use web_sys;
pub(crate) use app_context::AppContext;
pub use mount_procedure::{MountAction, MountProcedure, MountTarget};
thread_local! {
static CANCEL_CNT: RefCell<i32> = RefCell::new(0);
}
thread_local! {
static UPDATE_CNT: RefCell<i32> = RefCell::new(0);
}
mod app_context;
mod mount_procedure;
pub struct Program<APP>
where
APP: Application,
{
pub(crate) app_context: AppContext<APP>,
pub root_node: Rc<RefCell<Option<DomNode>>>,
pub(crate) mount_node: Rc<RefCell<Option<DomNode>>>,
pub(crate) pending_patches: Rc<RefCell<VecDeque<DomPatch>>>,
pub(crate) idle_callback_handles: Rc<RefCell<Vec<IdleCallbackHandle>>>,
pub(crate) animation_frame_handles: Rc<RefCell<Vec<AnimationFrameHandle>>>,
pub(crate) last_update: Rc<RefCell<Option<f64>>>,
}
pub struct WeakProgram<APP>
where
APP: Application,
{
pub(crate) app_context: WeakContext<APP>,
pub(crate) root_node: Weak<RefCell<Option<DomNode>>>,
mount_node: Weak<RefCell<Option<DomNode>>>,
pending_patches: Weak<RefCell<VecDeque<DomPatch>>>,
idle_callback_handles: Weak<RefCell<Vec<IdleCallbackHandle>>>,
animation_frame_handles: Weak<RefCell<Vec<AnimationFrameHandle>>>,
last_update: Weak<RefCell<Option<f64>>>,
}
impl<APP> WeakProgram<APP>
where
APP: Application,
{
pub fn upgrade(&self) -> Option<Program<APP>> {
let app_context = self.app_context.upgrade()?;
let root_node = self.root_node.upgrade()?;
let mount_node = self.mount_node.upgrade()?;
let pending_patches = self.pending_patches.upgrade()?;
let idle_callback_handles = self.idle_callback_handles.upgrade()?;
let animation_frame_handles = self.animation_frame_handles.upgrade()?;
let last_update = self.last_update.upgrade()?;
Some(Program {
app_context,
root_node,
mount_node,
pending_patches,
idle_callback_handles,
animation_frame_handles,
last_update,
})
}
}
impl<APP> Clone for WeakProgram<APP>
where
APP: Application,
{
fn clone(&self) -> Self {
WeakProgram {
app_context: self.app_context.clone(),
root_node: Weak::clone(&self.root_node),
mount_node: Weak::clone(&self.mount_node),
pending_patches: Weak::clone(&self.pending_patches),
idle_callback_handles: Weak::clone(&self.idle_callback_handles),
animation_frame_handles: Weak::clone(&self.animation_frame_handles),
last_update: Weak::clone(&self.last_update),
}
}
}
impl<APP> Program<APP>
where
APP: Application,
{
pub fn downgrade(&self) -> WeakProgram<APP> {
WeakProgram {
app_context: AppContext::downgrade(&self.app_context),
root_node: Rc::downgrade(&self.root_node),
mount_node: Rc::downgrade(&self.mount_node),
pending_patches: Rc::downgrade(&self.pending_patches),
idle_callback_handles: Rc::downgrade(&self.idle_callback_handles),
animation_frame_handles: Rc::downgrade(&self.animation_frame_handles),
last_update: Rc::downgrade(&self.last_update),
}
}
}
impl<APP> Clone for Program<APP>
where
APP: Application,
{
fn clone(&self) -> Self {
Program {
app_context: self.app_context.clone(),
root_node: Rc::clone(&self.root_node),
mount_node: Rc::clone(&self.mount_node),
pending_patches: Rc::clone(&self.pending_patches),
idle_callback_handles: Rc::clone(&self.idle_callback_handles),
animation_frame_handles: Rc::clone(&self.animation_frame_handles),
last_update: Rc::clone(&self.last_update),
}
}
}
impl<APP> Program<APP>
where
APP: Application,
{
pub fn app(&self) -> Ref<'_, APP> {
self.app_context.app.borrow()
}
pub fn app_mut(&self) -> RefMut<'_, APP> {
self.app_context.app.borrow_mut()
}
}
impl<APP> Program<APP>
where
APP: Application,
{
pub fn new(app: APP) -> Self {
Self::from_rc_app(Rc::new(RefCell::new(app)))
}
pub fn from_rc_app(app: Rc<RefCell<APP>>) -> Self {
let app_view = app.borrow().view();
Program {
app_context: AppContext {
app,
current_vdom: Rc::new(RefCell::new(app_view)),
pending_msgs: Rc::new(RefCell::new(VecDeque::new())),
pending_dispatches: Rc::new(RefCell::new(VecDeque::new())),
},
root_node: Rc::new(RefCell::new(None)),
mount_node: Rc::new(RefCell::new(None)),
pending_patches: Rc::new(RefCell::new(VecDeque::new())),
idle_callback_handles: Rc::new(RefCell::new(vec![])),
animation_frame_handles: Rc::new(RefCell::new(vec![])),
last_update: Rc::new(RefCell::new(None)),
}
}
fn after_mounted(&mut self) {
let init_cmd = self.app_context.init_app();
init_cmd.emit(self.clone());
self.inject_dynamic_style();
}
fn app_hash() -> u64 {
let type_id = TypeId::of::<APP>();
let mut hasher = DefaultHasher::new();
type_id.hash(&mut hasher);
hasher.finish()
}
fn inject_stylesheet(&mut self) {
let static_style = self.app_context.static_style();
if !static_style.is_empty() {
let class_names = format!("static {}", Self::app_hash());
self.inject_style(class_names, &static_style);
}
}
fn inject_dynamic_style(&mut self) {
let dynamic_style = self.app_context.dynamic_style();
if !dynamic_style.is_empty() {
let class_names = format!("dynamic {}", Self::app_hash());
self.inject_style(class_names, &dynamic_style);
}
}
pub fn append_to_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
let mut program = Self::new(app);
program.mount(mount_node, MountProcedure::append());
ManuallyDrop::new(program)
}
pub fn replace_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
let mut program = Self::new(app);
program.mount(mount_node, MountProcedure::replace());
ManuallyDrop::new(program)
}
pub fn clear_append_to_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
let mut program = Self::new(app);
program.mount(mount_node, MountProcedure::clear_append());
ManuallyDrop::new(program)
}
pub fn clear_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
Self::clear_append_to_mount(app, mount_node)
}
pub fn clear_mount_to_body(app: APP) -> ManuallyDrop<Self> {
Self::clear_append_to_mount(app, &body())
}
pub fn mount_to_body(app: APP) -> ManuallyDrop<Self> {
Self::append_to_mount(app, &body())
}
pub fn pre_mount(&mut self) {
self.inject_stylesheet();
}
#[allow(unused)]
pub(crate) fn create_initial_view(&self) -> DomNode {
let current_view = self.app_context.current_vdom();
let real_view = current_view.unwrap_template_ref();
self.create_dom_node(real_view)
}
pub fn mount(&mut self, mount_node: &web_sys::Node, mount_procedure: MountProcedure) {
let mount_node = DomNode::from(mount_node.clone());
*self.mount_node.borrow_mut() = Some(mount_node);
self.pre_mount();
let created_node = self.create_initial_view();
let mount_node: DomNode = match mount_procedure.target {
MountTarget::MountNode => self
.mount_node
.borrow()
.as_ref()
.expect("mount node")
.clone(),
MountTarget::ShadowRoot => {
let mount_element: web_sys::Element = self
.mount_node
.borrow()
.as_ref()
.expect("mount node")
.as_element();
mount_element
.attach_shadow(&web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open))
.expect("unable to attached shadow");
let mount_shadow = mount_element.shadow_root().expect("must have a shadow");
let shadow_node: web_sys::Node = mount_shadow.unchecked_into();
*self.mount_node.borrow_mut() = Some(DomNode::from(shadow_node));
self.mount_node
.borrow()
.as_ref()
.expect("mount_node")
.clone()
}
};
match mount_procedure.action {
MountAction::Append => {
mount_node.append_children(vec![created_node.clone()]);
}
MountAction::ClearAppend => {
mount_node.clear_children();
mount_node.append_children(vec![created_node.clone()]);
}
MountAction::Replace => {
mount_node.replace_node(created_node.clone());
}
}
*self.root_node.borrow_mut() = Some(created_node);
self.after_mounted();
}
#[cfg(feature = "with-ric")]
fn dispatch_pending_msgs_with_ric(&mut self) -> Result<(), JsValue> {
let program = Program::downgrade(&self);
let handle = request_idle_callback(move |deadline| {
let mut program = program.upgrade().expect("must upgrade");
program
.dispatch_pending_msgs(Some(deadline))
.expect("must execute")
})
.expect("must execute");
self.idle_callback_handles.borrow_mut().push(handle);
Ok(())
}
fn dispatch_pending_msgs(&mut self, deadline: Option<IdleDeadline>) -> Result<(), JsValue> {
if !self.app_context.has_pending_msgs() {
return Ok(());
}
let mut did_complete = true;
while self.app_context.dispatch_pending_msg() {
if let Some(deadline) = &deadline {
if deadline.did_timeout() {
did_complete = false;
break;
}
}
}
if !did_complete {
log::info!("did not complete pending msgs in time.. dispatching the rest");
#[cfg(feature = "with-ric")]
self.dispatch_pending_msgs_with_ric()
.expect("must complete");
}
Ok(())
}
pub fn update_dom(&mut self) -> Result<(), JsValue> {
let t1 = now();
if let Some(last_update) = self.last_update.borrow().as_ref() {
let frame_time = (1000_f64 / 60_f64).floor(); let time_delta = t1 - last_update;
let remaining = frame_time - time_delta;
if time_delta < frame_time {
log::warn!("update is {remaining} too soon!... time_delta: {time_delta}, frame_time: {frame_time}");
let mut program = self.clone();
crate::dom::request_timeout_callback(
move||{
program.update_dom().unwrap();
}, remaining.round() as i32).unwrap();
log::info!("update is cancelled..");
CANCEL_CNT.with_borrow_mut(|c|*c += 1);
return Ok(())
}
}
log::info!("Doing and update...");
UPDATE_CNT.with_borrow_mut(|c|*c += 1);
log::info!("ratio(cancelled/update): {}/{}", CANCEL_CNT.with_borrow(|c|*c), UPDATE_CNT.with_borrow(|c|*c));
let view = self.app_context.view();
let t2 = now();
let node_count = view.node_count();
let skip_diff = view.skip_diff();
let dom_patches = if let Some(skip_diff) = skip_diff {
let current_vdom = self.app_context.current_vdom();
let real_current_vdom = current_vdom.unwrap_template_ref();
let real_view = view.unwrap_template_ref();
let patches =
self.create_patches_with_skip_diff(real_current_vdom, real_view, &skip_diff);
#[cfg(all(feature = "with-debug", feature = "log-patches"))]
{
log::info!("There are {} patches", patches.len());
log::info!("patches: {patches:#?}");
}
self.convert_patches(
self.root_node
.borrow()
.as_ref()
.expect("must have a root node"),
&patches,
)
.expect("must convert patches")
} else {
self.create_dom_patch(&view)
};
let total_patches = dom_patches.len();
self.queue_dom_patches(dom_patches).expect("must not error");
self.app_context.set_current_dom(view);
let t3 = now();
let strong_count = self.app_context.strong_count();
let weak_count = self.app_context.weak_count();
let measurements = Measurements {
node_count,
build_view_took: t2 - t1,
total_patches,
dom_update_took: t3 - t2,
total_time: t3 - t1,
strong_count,
weak_count,
};
if measurements.total_time > 16.0 {
#[cfg(all(feature = "with-measure", feature = "with-debug"))]
{
log::warn!("dispatch took {}ms", measurements.total_time.round());
}
}
#[cfg(feature = "with-measure")]
self.app_context.measurements(measurements);
*self.last_update.borrow_mut() = Some(t3);
Ok(())
}
pub fn queue_dom_patches(&mut self, dom_patches: Vec<DomPatch>) -> Result<(), JsValue> {
self.pending_patches.borrow_mut().extend(dom_patches);
#[cfg(feature = "with-raf")]
self.apply_pending_patches_with_raf().expect("raf");
#[cfg(not(feature = "with-raf"))]
self.apply_pending_patches().expect("raf");
Ok(())
}
pub(crate) fn create_patches_with_skip_diff<'a>(
&self,
old_vdom: &'a vdom::Node<APP::MSG>,
new_vdom: &'a vdom::Node<APP::MSG>,
skip_diff: &SkipDiff,
) -> Vec<Patch<'a, APP::MSG>> {
use crate::vdom::TreePath;
assert!(!old_vdom.is_template(), "old vdom should not be a template");
assert!(!new_vdom.is_template(), "new vdom should not be a template");
diff_recursive(
old_vdom,
new_vdom,
&SkipPath::new(TreePath::root(), skip_diff.clone()),
)
}
fn create_dom_patch(&self, new_vdom: &vdom::Node<APP::MSG>) -> Vec<DomPatch> {
let current_vdom = self.app_context.current_vdom();
let patches = diff(¤t_vdom, new_vdom);
#[cfg(all(feature = "with-debug", feature = "log-patches"))]
{
log::debug!("There are {} patches", patches.len());
log::debug!("patches: {patches:#?}");
}
self.convert_patches(
self.root_node
.borrow()
.as_ref()
.expect("must have a root node"),
&patches,
)
.expect("must convert patches")
}
#[cfg(feature = "with-raf")]
fn apply_pending_patches_with_raf(&mut self) -> Result<(), JsValue> {
let program = Program::downgrade(&self);
let handle = request_animation_frame(move || {
let mut program = program.upgrade().expect("must upgrade");
program.apply_pending_patches().expect("must not error");
})
.expect("must execute");
self.animation_frame_handles.borrow_mut().push(handle);
Ok(())
}
fn apply_pending_patches(&mut self) -> Result<(), JsValue> {
if self.pending_patches.borrow().is_empty() {
return Ok(());
}
let dom_patches: Vec<DomPatch> = self.pending_patches.borrow_mut().drain(..).collect();
self.apply_dom_patches(dom_patches)?;
Ok(())
}
#[cfg(feature = "with-ric")]
fn dispatch_inner_with_ric(&self) {
let program = Program::downgrade(&self);
let handle = request_idle_callback(move |deadline| {
if let Some(mut program) = program.upgrade() {
program.dispatch_inner(Some(deadline));
} else {
log::warn!("unable to upgrade program.. maybe try again next time..");
}
})
.expect("must execute");
self.idle_callback_handles.borrow_mut().push(handle);
}
#[allow(unused)]
#[cfg(feature = "with-raf")]
fn dispatch_inner_with_raf(&self) {
let program = Program::downgrade(&self);
let handle = request_animation_frame(move || {
let mut program = program.upgrade().expect("must upgrade");
program.dispatch_inner(None);
})
.expect("must execute");
self.animation_frame_handles.borrow_mut().push(handle);
}
fn dispatch_inner_with_priority_ric(&self) {
#[cfg(feature = "with-ric")]
self.dispatch_inner_with_ric();
#[cfg(not(feature = "with-ric"))]
{
#[cfg(feature = "with-raf")]
self.dispatch_inner_with_raf();
#[cfg(not(feature = "with-raf"))]
{
let program = Program::downgrade(self);
wasm_bindgen_futures::spawn_local(async move {
if let Some(mut program) = program.upgrade() {
program.dispatch_inner(None);
} else {
log::warn!(
"unable to upgrade program here, in dispatch_inner_with_priority_ric"
);
}
})
}
}
}
fn dispatch_inner(&mut self, deadline: Option<IdleDeadline>) {
self.dispatch_pending_msgs(deadline)
.expect("must dispatch msgs");
#[cfg(feature = "ensure-check")]
if self.app_context.has_pending_msgs() {
log::info!(
"There are still: {} pending msgs",
self.app_context.pending_msgs_count()
);
self.dispatch_pending_msgs(None)
.expect("must dispatch all pending msgs");
}
#[cfg(feature = "ensure-check")]
if self.app_context.has_pending_msgs() {
panic!("Can not proceed until previous pending msgs are dispatched..");
}
let cmd = self.app_context.batch_pending_cmds();
if !self.pending_patches.borrow().is_empty() {
log::error!(
"BEFORE DOM updates there are still Remaining pending patches: {}",
self.pending_patches.borrow().len()
);
}
self.update_dom().expect("must update dom");
#[cfg(feature = "ensure-check")]
if !self.pending_patches.borrow().is_empty() {
self.apply_pending_patches()
.expect("applying pending patches..");
}
#[cfg(feature = "ensure-check")]
if !self.pending_patches.borrow().is_empty() {
log::error!(
"Remaining pending patches: {}",
self.pending_patches.borrow().len()
);
panic!(
"There are still pending patches.. can not emit cmd, if all pending patches
has not been applied yet!"
);
}
cmd.emit(self.clone());
}
fn inject_style(&mut self, class_names: String, style: &str) {
let style_node = html::tags::style([class(class_names)], [text(style)]);
let created_node = self.create_dom_node(&style_node);
let head = document().head().expect("must have a head");
let head_node: web_sys::Node = head.unchecked_into();
let dom_head = DomNode::from(head_node);
dom_head.append_children(vec![created_node]);
}
pub fn inject_style_to_mount(&mut self, style: &str) {
let style_node = html::tags::style([], [text(style)]);
let created_node = self.create_dom_node(&style_node);
self.mount_node
.borrow_mut()
.as_mut()
.expect("mount node")
.append_children(vec![created_node]);
}
pub fn dispatch_multiple(&mut self, msgs: impl IntoIterator<Item = APP::MSG>) {
self.app_context.push_msgs(msgs);
self.dispatch_inner_with_priority_ric();
}
pub fn dispatch(&mut self, msg: APP::MSG) {
self.dispatch_multiple([msg])
}
}
impl<APP> Program<APP>
where
APP: Application,
{
#[cfg(feature = "test-fixtures")]
pub fn update_dom_with_vdom(
&mut self,
new_vdom: vdom::Node<APP::MSG>,
) -> Result<usize, JsValue> {
let dom_patches = self.create_dom_patch(&new_vdom);
let total_patches = dom_patches.len();
self.pending_patches.borrow_mut().extend(dom_patches);
self.apply_pending_patches().expect("raf");
self.app_context.set_current_dom(new_vdom);
Ok(total_patches)
}
}