lib.rs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. use captuered_context::CapturedContext;
  2. use dioxus_core::{NodeFactory, SchedulerMsg, VNode};
  3. use dioxus_hooks::UnboundedSender;
  4. use error::Error;
  5. use interperter::build;
  6. use lazy_static::lazy_static;
  7. use serde::{Deserialize, Serialize};
  8. use std::collections::HashMap;
  9. use std::panic::Location;
  10. use std::sync::{RwLock, RwLockReadGuard};
  11. use syn::parse_str;
  12. mod attributes;
  13. pub mod captuered_context;
  14. mod elements;
  15. pub mod error;
  16. mod interperter;
  17. lazy_static! {
  18. /// This a a global store of the current rsx text for each call to rsx
  19. // Global mutable data is genrally not great, but it allows users to not worry about passing down the text RsxContex every time they switch to hot reloading.
  20. pub static ref RSX_CONTEXT: RsxContext = RsxContext::new(RsxData::default());
  21. }
  22. // the location of the code relative to the current crate based on [std::panic::Location]
  23. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  24. pub struct CodeLocation {
  25. pub file: String,
  26. pub line: u32,
  27. pub column: u32,
  28. }
  29. /// Get the resolved rsx given the origional rsx, a captured context of dynamic components, and a factory to build the resulting node
  30. pub fn resolve_scope<'a>(
  31. location: CodeLocation,
  32. rsx: &'static str,
  33. captured: CapturedContext<'a>,
  34. factory: NodeFactory<'a>,
  35. ) -> VNode<'a> {
  36. let rsx_text_index = &*RSX_CONTEXT;
  37. // only the insert the rsx text once
  38. if !rsx_text_index.read().hm.contains_key(&location) {
  39. rsx_text_index.insert(location.clone(), rsx.to_string());
  40. }
  41. if let Some(text) = {
  42. let read = rsx_text_index.read();
  43. // clone prevents deadlock on nested rsx calls
  44. read.hm.get(&location).cloned()
  45. } {
  46. match interpert_rsx(factory, &text, captured) {
  47. Ok(vnode) => vnode,
  48. Err(err) => {
  49. rsx_text_index.report_error(err);
  50. factory.text(format_args!(""))
  51. }
  52. }
  53. } else {
  54. panic!("rsx: line number {:?} not found in rsx index", location);
  55. }
  56. }
  57. fn interpert_rsx<'a, 'b>(
  58. factory: dioxus_core::NodeFactory<'a>,
  59. text: &str,
  60. context: captuered_context::CapturedContext<'a>,
  61. ) -> Result<VNode<'a>, Error> {
  62. build(
  63. parse_str(text).map_err(|err| Error::ParseError(err))?,
  64. context,
  65. &factory,
  66. )
  67. }
  68. /// get the code location of the code that called this function
  69. #[track_caller]
  70. pub fn get_line_num() -> CodeLocation {
  71. let location = Location::caller();
  72. CodeLocation {
  73. file: location.file().to_string(),
  74. line: location.line(),
  75. column: location.column(),
  76. }
  77. }
  78. /// A handle to the rsx context with interior mutability
  79. #[derive(Debug)]
  80. pub struct RsxContext {
  81. data: RwLock<RsxData>,
  82. }
  83. /// A store of the text for the rsx macro for each call to rsx
  84. #[derive(Default)]
  85. pub struct RsxData {
  86. pub hm: HashMap<CodeLocation, String>,
  87. pub error_handler: Option<Box<dyn ErrorHandler>>,
  88. pub scheduler_channel: Option<UnboundedSender<SchedulerMsg>>,
  89. }
  90. impl std::fmt::Debug for RsxData {
  91. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  92. f.debug_struct("RsxData").field("hm", &self.hm).finish()
  93. }
  94. }
  95. impl RsxContext {
  96. pub fn new(data: RsxData) -> Self {
  97. Self {
  98. data: RwLock::new(data),
  99. }
  100. }
  101. /// Set the text for an rsx call at some location
  102. pub fn insert(&self, loc: CodeLocation, text: String) {
  103. let mut write = self.data.write().unwrap();
  104. write.hm.insert(loc, text);
  105. if let Some(channel) = &mut write.scheduler_channel {
  106. channel.unbounded_send(SchedulerMsg::DirtyAll).unwrap()
  107. }
  108. }
  109. fn read(&self) -> RwLockReadGuard<RsxData> {
  110. self.data.read().unwrap()
  111. }
  112. fn report_error(&self, error: Error) {
  113. if let Some(handler) = &self.data.write().unwrap().error_handler {
  114. handler.handle_error(error)
  115. }
  116. }
  117. /// Set the handler for errors interperting the rsx
  118. pub fn set_error_handler(&self, handler: impl ErrorHandler + 'static) {
  119. self.data.write().unwrap().error_handler = Some(Box::new(handler));
  120. }
  121. /// Provide the scduler channel from [dioxus_code::VirtualDom::get_scheduler_channel].
  122. /// The channel allows the interpreter to force re-rendering of the dom when the rsx is changed.
  123. pub fn provide_scheduler_channel(&self, channel: UnboundedSender<SchedulerMsg>) {
  124. self.data.write().unwrap().scheduler_channel = Some(channel)
  125. }
  126. }
  127. /// A error handler for errors reported by the rsx interperter
  128. pub trait ErrorHandler: Send + Sync {
  129. fn handle_error(&self, err: Error);
  130. }
  131. #[derive(Serialize, Deserialize, Clone, Debug)]
  132. pub struct SetRsxMessage {
  133. pub location: CodeLocation,
  134. pub new_text: String,
  135. }