zng_ext_hot_reload/
node.rs

1use std::{any::Any, sync::Arc};
2
3use zng_app::{
4    render::{FrameBuilder, FrameUpdate},
5    update::{EventUpdate, WidgetUpdates},
6    widget::{
7        WIDGET, WidgetUpdateMode,
8        info::{WidgetInfoBuilder, WidgetLayout, WidgetMeasure},
9        node::{ArcNode, ArcNodeList, BoxedUiNode, BoxedUiNodeList, NilUiNode, UiNode, UiNodeList},
10    },
11};
12use zng_app_context::LocalContext;
13use zng_unit::PxSize;
14use zng_var::{BoxedVar, IntoValue, IntoVar, Var, VarValue};
15
16use crate::{HOT_RELOAD, HOT_RELOAD_EVENT};
17
18trait Arg: Any + Send {
19    fn clone_boxed(&self) -> Box<dyn Arg>;
20    fn into_any(self: Box<Self>) -> Box<dyn Any>;
21}
22impl<T: VarValue> Arg for BoxedVar<T> {
23    fn clone_boxed(&self) -> Box<dyn Arg> {
24        Box::new(self.clone())
25    }
26
27    fn into_any(self: Box<Self>) -> Box<dyn Any> {
28        self
29    }
30}
31#[derive(Clone)]
32struct ValueArg<T>(T);
33impl<T: Clone + Send + Any> Arg for ValueArg<T> {
34    fn clone_boxed(&self) -> Box<dyn Arg> {
35        Box::new(self.clone())
36    }
37
38    fn into_any(self: Box<Self>) -> Box<dyn Any> {
39        self
40    }
41}
42impl Arg for ArcNode<BoxedUiNode> {
43    fn clone_boxed(&self) -> Box<dyn Arg> {
44        Box::new(self.clone())
45    }
46
47    fn into_any(self: Box<Self>) -> Box<dyn Any> {
48        self
49    }
50}
51impl Arg for ArcNodeList<BoxedUiNodeList> {
52    fn clone_boxed(&self) -> Box<dyn Arg> {
53        Box::new(self.clone())
54    }
55
56    fn into_any(self: Box<Self>) -> Box<dyn Any> {
57        self
58    }
59}
60
61/// Arguments for hot node.
62#[doc(hidden)]
63pub struct HotNodeArgs {
64    args: Vec<Box<dyn Arg>>,
65}
66impl HotNodeArgs {
67    pub fn with_capacity(capacity: usize) -> Self {
68        Self {
69            args: Vec::with_capacity(capacity),
70        }
71    }
72
73    pub fn push_var<T: VarValue>(&mut self, arg: impl IntoVar<T>) {
74        let arg = arg.into_var().boxed();
75        self.args.push(Box::new(arg));
76    }
77
78    pub fn push_value<T: VarValue>(&mut self, arg: impl IntoValue<T>) {
79        let arg = ValueArg(arg.into());
80        self.args.push(Box::new(arg))
81    }
82
83    pub fn push_ui_node(&mut self, arg: impl UiNode) {
84        let arg = ArcNode::new(arg.boxed());
85        self.args.push(Box::new(arg))
86    }
87
88    pub fn push_ui_node_list(&mut self, arg: impl UiNodeList) {
89        let arg = ArcNodeList::new(arg.boxed());
90        self.args.push(Box::new(arg))
91    }
92
93    pub fn push_clone<T: Clone + Send + Any>(&mut self, arg: T) {
94        let arg = ValueArg(arg);
95        self.args.push(Box::new(arg));
96    }
97
98    fn pop_downcast<T: Any>(&mut self) -> T {
99        *self.args.pop().unwrap().into_any().downcast().unwrap()
100    }
101
102    pub fn pop_var<T: VarValue>(&mut self) -> BoxedVar<T> {
103        self.pop_downcast()
104    }
105
106    pub fn pop_value<T: VarValue>(&mut self) -> T {
107        self.pop_downcast::<ValueArg<T>>().0
108    }
109
110    pub fn pop_ui_node(&mut self) -> BoxedUiNode {
111        self.pop_downcast::<ArcNode<BoxedUiNode>>().take_on_init().boxed()
112    }
113
114    pub fn pop_ui_node_list(&mut self) -> BoxedUiNodeList {
115        self.pop_downcast::<ArcNodeList<BoxedUiNodeList>>().take_on_init().boxed()
116    }
117
118    pub fn pop_clone<T: Clone + Send + Any>(&mut self) -> T {
119        self.pop_downcast::<ValueArg<T>>().0
120    }
121}
122impl Clone for HotNodeArgs {
123    fn clone(&self) -> Self {
124        let mut r = Self { args: vec![] };
125        r.clone_from(self);
126        r
127    }
128
129    fn clone_from(&mut self, source: &Self) {
130        self.args.clear();
131        self.args.reserve(source.args.len());
132        for a in &source.args {
133            self.args.push(a.clone_boxed());
134        }
135    }
136}
137
138/// Hot node host, dynamically re-inits the widget when the library rebuilds.
139///
140/// Captures and propagates the `LocalContext` because `static` variables are not the same
141/// in the dynamically loaded library.
142#[doc(hidden)]
143pub struct HotNodeHost {
144    manifest_dir: &'static str,
145    name: &'static str,
146    args: HotNodeArgs,
147    fallback: fn(HotNodeArgs) -> HotNode,
148    instance: HotNode,
149}
150impl HotNodeHost {
151    pub fn new(manifest_dir: &'static str, name: &'static str, args: HotNodeArgs, fallback: fn(HotNodeArgs) -> HotNode) -> Self {
152        Self {
153            manifest_dir,
154            name,
155            args,
156            fallback,
157            instance: HotNode::new(NilUiNode),
158        }
159    }
160}
161impl UiNode for HotNodeHost {
162    fn init(&mut self) {
163        WIDGET.sub_event(&HOT_RELOAD_EVENT);
164
165        let mut ctx = LocalContext::capture();
166
167        self.instance = match HOT_RELOAD.lib(self.manifest_dir) {
168            Some(lib) => match lib.instantiate(self.name, &mut ctx, self.args.clone()) {
169                Some(ok) => {
170                    tracing::debug!("loaded hot `{}` in `{}`", self.name, WIDGET.trace_id());
171                    ok
172                }
173                None => {
174                    tracing::error!("hot node `{}` not found in `{}` library", self.name, self.manifest_dir);
175                    (self.fallback)(self.args.clone())
176                }
177            },
178            None => {
179                tracing::debug!("hot lib `{}` not loaded yet", self.manifest_dir);
180                (self.fallback)(self.args.clone())
181            }
182        };
183
184        self.instance.init(&mut ctx);
185    }
186
187    fn deinit(&mut self) {
188        let mut ctx = LocalContext::capture();
189        self.instance.deinit(&mut ctx);
190        self.instance = HotNode::new(NilUiNode);
191    }
192
193    fn info(&mut self, info: &mut WidgetInfoBuilder) {
194        let mut ctx = LocalContext::capture();
195        self.instance.info(&mut ctx, info);
196    }
197
198    fn event(&mut self, update: &EventUpdate) {
199        let mut ctx = LocalContext::capture();
200        self.instance.event(&mut ctx, update);
201
202        if let Some(args) = HOT_RELOAD_EVENT.on(update) {
203            if args.lib.manifest_dir() == self.manifest_dir {
204                WIDGET.reinit();
205                tracing::debug!("reinit `{}` to hot reload `{}`", WIDGET.trace_id(), self.name);
206            }
207        }
208    }
209
210    fn update(&mut self, updates: &WidgetUpdates) {
211        let mut ctx = LocalContext::capture();
212        self.instance.update(&mut ctx, updates);
213    }
214
215    fn measure(&mut self, wm: &mut WidgetMeasure) -> PxSize {
216        let mut ctx = LocalContext::capture();
217        self.instance.measure(&mut ctx, wm)
218    }
219
220    fn layout(&mut self, wl: &mut WidgetLayout) -> PxSize {
221        let mut ctx = LocalContext::capture();
222        self.instance.layout(&mut ctx, wl)
223    }
224
225    fn render(&mut self, frame: &mut FrameBuilder) {
226        let mut ctx = LocalContext::capture();
227        self.instance.render(&mut ctx, frame)
228    }
229
230    fn render_update(&mut self, update: &mut FrameUpdate) {
231        let mut ctx = LocalContext::capture();
232        self.instance.render_update(&mut ctx, update)
233    }
234
235    fn is_widget(&self) -> bool {
236        let mut ctx = LocalContext::capture();
237        self.instance.is_widget(&mut ctx)
238    }
239
240    fn is_nil(&self) -> bool {
241        let mut ctx = LocalContext::capture();
242        self.instance.is_nil(&mut ctx)
243    }
244
245    fn with_context<R, F>(&mut self, update_mode: WidgetUpdateMode, f: F) -> Option<R>
246    where
247        F: FnOnce() -> R,
248    {
249        let mut ctx = LocalContext::capture();
250        let mut r = None;
251        let mut f = Some(f);
252        self.instance.with_context(&mut ctx, update_mode, &mut || {
253            r = Some(f.take().unwrap()());
254        });
255        r
256    }
257}
258
259/// Hot loaded node.
260#[doc(hidden)]
261pub struct HotNode {
262    child: BoxedUiNode,
263    api: HotNodeApi,
264    // keep alive because `child` is code from it.
265    pub(crate) _lib: Option<Arc<libloading::Library>>,
266}
267impl HotNode {
268    pub fn new(node: impl UiNode) -> Self {
269        Self {
270            child: node.boxed(),
271            api: HotNodeApi::capture(),
272            _lib: None,
273        }
274    }
275
276    fn init(&mut self, ctx: &mut LocalContext) {
277        (self.api.init)(&mut self.child, ctx)
278    }
279
280    fn deinit(&mut self, ctx: &mut LocalContext) {
281        (self.api.deinit)(&mut self.child, ctx)
282    }
283
284    fn info(&mut self, ctx: &mut LocalContext, info: &mut WidgetInfoBuilder) {
285        (self.api.info)(&mut self.child, ctx, info)
286    }
287
288    fn event(&mut self, ctx: &mut LocalContext, update: &EventUpdate) {
289        (self.api.event)(&mut self.child, ctx, update)
290    }
291
292    fn update(&mut self, ctx: &mut LocalContext, updates: &WidgetUpdates) {
293        (self.api.update)(&mut self.child, ctx, updates)
294    }
295
296    fn measure(&mut self, ctx: &mut LocalContext, wm: &mut WidgetMeasure) -> PxSize {
297        (self.api.measure)(&mut self.child, ctx, wm)
298    }
299
300    fn layout(&mut self, ctx: &mut LocalContext, wl: &mut WidgetLayout) -> PxSize {
301        (self.api.layout)(&mut self.child, ctx, wl)
302    }
303
304    fn render(&mut self, ctx: &mut LocalContext, frame: &mut FrameBuilder) {
305        (self.api.render)(&mut self.child, ctx, frame)
306    }
307
308    fn render_update(&mut self, ctx: &mut LocalContext, update: &mut FrameUpdate) {
309        (self.api.render_update)(&mut self.child, ctx, update)
310    }
311
312    fn is_widget(&self, ctx: &mut LocalContext) -> bool {
313        (self.api.is_widget)(&self.child, ctx)
314    }
315
316    fn is_nil(&self, ctx: &mut LocalContext) -> bool {
317        (self.api.is_nil)(&self.child, ctx)
318    }
319
320    fn with_context(&mut self, ctx: &mut LocalContext, update_mode: WidgetUpdateMode, f: &mut dyn FnMut()) {
321        (self.api.with_context)(&mut self.child, ctx, update_mode, f)
322    }
323}
324
325// HotNode "methods" references from the dynamic loaded code to be called from the static code.
326struct HotNodeApi {
327    init: fn(&mut BoxedUiNode, &mut LocalContext),
328    deinit: fn(&mut BoxedUiNode, &mut LocalContext),
329    info: fn(&mut BoxedUiNode, &mut LocalContext, &mut WidgetInfoBuilder),
330    event: fn(&mut BoxedUiNode, &mut LocalContext, &EventUpdate),
331    update: fn(&mut BoxedUiNode, &mut LocalContext, &WidgetUpdates),
332    measure: fn(&mut BoxedUiNode, &mut LocalContext, &mut WidgetMeasure) -> PxSize,
333    layout: fn(&mut BoxedUiNode, &mut LocalContext, &mut WidgetLayout) -> PxSize,
334    render: fn(&mut BoxedUiNode, &mut LocalContext, &mut FrameBuilder),
335    render_update: fn(&mut BoxedUiNode, &mut LocalContext, &mut FrameUpdate),
336    is_widget: fn(&BoxedUiNode, &mut LocalContext) -> bool,
337    is_nil: fn(&BoxedUiNode, &mut LocalContext) -> bool,
338    with_context: fn(&mut BoxedUiNode, &mut LocalContext, WidgetUpdateMode, &mut dyn FnMut()),
339}
340impl HotNodeApi {
341    fn init(child: &mut BoxedUiNode, ctx: &mut LocalContext) {
342        ctx.with_context(|| child.init())
343    }
344
345    fn deinit(child: &mut BoxedUiNode, ctx: &mut LocalContext) {
346        ctx.with_context(|| child.deinit())
347    }
348
349    fn info(child: &mut BoxedUiNode, ctx: &mut LocalContext, info: &mut WidgetInfoBuilder) {
350        ctx.with_context(|| child.info(info))
351    }
352
353    fn event(child: &mut BoxedUiNode, ctx: &mut LocalContext, update: &EventUpdate) {
354        ctx.with_context(|| child.event(update))
355    }
356
357    fn update(child: &mut BoxedUiNode, ctx: &mut LocalContext, updates: &WidgetUpdates) {
358        ctx.with_context(|| child.update(updates))
359    }
360
361    fn measure(child: &mut BoxedUiNode, ctx: &mut LocalContext, wm: &mut WidgetMeasure) -> PxSize {
362        ctx.with_context(|| child.measure(wm))
363    }
364
365    fn layout(child: &mut BoxedUiNode, ctx: &mut LocalContext, wl: &mut WidgetLayout) -> PxSize {
366        ctx.with_context(|| child.layout(wl))
367    }
368
369    fn render(child: &mut BoxedUiNode, ctx: &mut LocalContext, frame: &mut FrameBuilder) {
370        ctx.with_context(|| child.render(frame))
371    }
372
373    fn render_update(child: &mut BoxedUiNode, ctx: &mut LocalContext, update: &mut FrameUpdate) {
374        ctx.with_context(|| child.render_update(update))
375    }
376
377    fn is_widget(child: &BoxedUiNode, ctx: &mut LocalContext) -> bool {
378        ctx.with_context(|| child.is_widget())
379    }
380
381    fn is_nil(child: &BoxedUiNode, ctx: &mut LocalContext) -> bool {
382        ctx.with_context(|| child.is_nil())
383    }
384
385    fn with_context(child: &mut BoxedUiNode, ctx: &mut LocalContext, update_mode: WidgetUpdateMode, f: &mut dyn FnMut()) {
386        ctx.with_context(|| {
387            child.with_context(update_mode, f);
388        })
389    }
390
391    fn capture() -> Self {
392        Self {
393            init: Self::init,
394            deinit: Self::deinit,
395            info: Self::info,
396            event: Self::event,
397            update: Self::update,
398            measure: Self::measure,
399            layout: Self::layout,
400            render: Self::render,
401            render_update: Self::render_update,
402            is_widget: Self::is_widget,
403            is_nil: Self::is_nil,
404            with_context: Self::with_context,
405        }
406    }
407}