1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! TimelineView — driving redisplay from Rust state.
use super::*;
use crate::abi;
use crate::resolve;
use core::ffi::c_void;
use crate::mangle;
// ── TimelineView — driving redisplay from Rust state ──
mangle::result_types! {
/// `TimelineView<AnimationTimelineSchedule, AnyView>`
/// `TimelineView<AnimationTimelineSchedule, AnyView>`
pub(crate) TIMELINE_T = ty("TimelineView").of(vec![ty("AnimationTimelineSchedule"), ty("AnyView")]);
}
/// The Rust side of a `TimelineView` content closure.
pub(crate) type TimelineBuilder = Box<dyn Fn() -> ViewHandle>;
/// `(Context) -> Content`. The context is ignored: the point is the *call*, not
/// its argument — SwiftUI invoking this is what re-reads Rust state.
pub(crate) unsafe extern "C" fn emit_timeline_view(out: *mut u8, _context: i64, payload: *mut c_void) {
let typed = unsafe { &*(payload as *const TypedTimelineBuilder) };
let view = (typed.builder)();
unsafe {
if typed.meta.is_null() {
*(out as *mut u64) = view.into_anyview_word();
return;
}
// SwiftUI sized the content for what the probe produced. Same rule as a
// `ForEach` row and the same one Swift enforces by making the closure's
// result `some View`: one type, with `view_if!` for a branch.
assert_eq!(
view.meta, typed.meta,
"a TimelineView builder returned {} after returning {} — it has to be \
one type. Use `view_if!` for a branch.",
crate::diag::type_name(view.meta),
crate::diag::type_name(typed.meta),
);
let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(view.meta);
if vwt.size > 0 {
(vwt.initialize_with_copy)(out as *mut c_void, view.value_ptr() as *mut c_void, view.meta);
}
}
}
/// A content builder together with the type it was found to produce.
pub(crate) struct TypedTimelineBuilder {
pub(crate) builder: TimelineBuilder,
pub(crate) meta: *const c_void,
}
/// `TimelineView(.animation) { … }` — rebuild the content on SwiftUI's clock.
///
/// This is the answer to triggering. Assigning a hosting view's `rootView`
/// *replaces* the tree, so SwiftUI has nothing to interpolate and no reason to
/// treat a change as a change — which is why an external mutation never
/// animated. A `TimelineView` inverts it: SwiftUI calls back on every display
/// frame, so the builder re-reads Rust state and the interface follows it with
/// no `set_root` at all.
///
/// ```ignore
/// let state = BoundState::new(0.0);
/// let ui = timeline({
/// let s = state.clone();
/// move || views::offset(&views::circle(), s.get(), 0.0)
/// });
/// // mutating `state` from Rust now shows up by itself
/// ```
///
/// The builder is leaked: SwiftUI keeps calling it for as long as the view lives.
pub fn timeline(builder: impl Fn() -> ViewHandle + 'static) -> ViewHandle {
unsafe {
let boxed: TimelineBuilder = Box::new(builder);
// Ask the builder what it makes. It takes no argument, so there is
// nothing to fabricate — but it is an extra call, and a builder with
// side effects will notice.
let probe = boxed();
let (probe_meta, probe_wt) = (probe.meta, probe.wt);
drop(probe);
let payload =
Box::into_raw(Box::new(TypedTimelineBuilder { builder: boxed, meta: core::ptr::null() }))
as *mut c_void;
let ctx = Box::leak(Box::new(abi::IndexClosureCtx::new(emit_timeline_view, payload)))
as *mut abi::IndexClosureCtx;
// AnimationTimelineSchedule(minimumInterval: nil, paused: false)
// x0 = Optional<Double> payload, x1 = tag (1 = nil), x2 = paused,
// x8 = @out
let sched_meta = abi::metadata0(resolve::anim_schedule_ma());
let mut schedule =
vec![0u8; abi::type_bytes(sched_meta)];
core::arch::asm!("blr {f}", f = in(reg) resolve::anim_schedule_init(),
in("x0") 0u64, in("x1") 1u64, in("x2") 0u64,
in("x8") schedule.as_mut_ptr(),
in("x20") sched_meta,
lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
clobber_abi("C"),
);
let sched_wt =
abi::get_view_wt_for_proto(sched_meta, resolve::timeline_schedule_proto());
assert!(!sched_wt.is_null(), "AnimationTimelineSchedule: TimelineSchedule");
let (known_meta, known_wt, known_size) = result_type(&TIMELINE_T);
// `TimelineView<Schedule, Content>`: two parameters, then their
// conformances, so the content is at 1 and its `View` witness at 3.
let typed = super::substitute_content(known_meta, 4, 1, 3, probe_meta, probe_wt);
let (meta, wt) = typed.unwrap_or((known_meta, known_wt));
let size = if typed.is_some() { abi::type_bytes(meta) } else { known_size };
let (content_meta, content_wt) = match typed {
Some(_) => (probe_meta, probe_wt),
None => (resolve::anyview_meta(), resolve::anyview_wt()),
};
// Publish the promise only once the instantiation actually took.
(*(payload as *mut TypedTimelineBuilder)).meta =
if typed.is_some() { probe_meta } else { core::ptr::null() };
let mut result = vec![0u8; size];
// TimelineView.init(_:content:)
// x0 = @in schedule, x1 = closure fn, x2 = closure ctx,
// x3 = Schedule.Type, x4 = Content.Type,
// x5 = Schedule: TimelineSchedule WT, x6 = Content: View WT, x8 = @out
core::arch::asm!("blr {f}", f = in(reg) resolve::timeline_init(),
in("x0") schedule.as_mut_ptr(),
in("x1") abi::index_closure_thunk as *const c_void,
in("x2") ctx,
in("x3") sched_meta,
in("x4") content_meta,
in("x5") sched_wt,
in("x6") content_wt,
in("x8") result.as_mut_ptr(),
lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
clobber_abi("C"),
);
wrap_owned(result, meta, wt)
}
}