use crate::abi;
use crate::conformance::{synthesize_for, Conformance, Conformer, Protocol};
use crate::keypath;
use crate::objc;
use crate::resolve;
use crate::views::ViewHandle;
use core::ffi::c_void;
use std::sync::{Arc, Mutex, OnceLock};
struct Kind {
class: usize,
metadata: usize,
observable_wt: usize,
}
unsafe impl Send for Kind {}
unsafe impl Sync for Kind {}
fn kind() -> &'static Kind {
static K: OnceLock<Kind> = OnceLock::new();
K.get_or_init(|| unsafe {
let class = objc::ClassBuilder::new(c"NSObject", c"RustObservable")
.expect("the class name `RustObservable` is already taken")
.register();
let metadata = swift_runtime_sys::MetadataIntrospection::swift_getObjCClassMetadata(class);
assert!(!metadata.is_null(), "RustObservable metadata");
let protocol = Protocol::from_symbol(c"$s11Observation10ObservableMp");
assert_eq!(
protocol.requirement_count(),
0,
"Observable used to be a marker protocol with nothing to implement",
);
let conformance: &'static Conformance = Box::leak(Box::new(synthesize_for(
&protocol,
Conformer::ObjCClass(class),
&[],
)));
Kind {
class: class as usize,
metadata: metadata as usize,
observable_wt: conformance.witness_table() as usize,
}
})
}
struct Named {
name: String,
value_type: core::any::TypeId,
key_path: u64,
}
fn names() -> &'static Mutex<Vec<Named>> {
static N: OnceLock<Mutex<Vec<Named>>> = OnceLock::new();
N.get_or_init(|| Mutex::new(Vec::new()))
}
type WriteInitial = Box<dyn Fn(*mut u8) + Send + Sync>;
fn initials() -> &'static Mutex<Vec<Option<WriteInitial>>> {
static I: OnceLock<Mutex<Vec<Option<WriteInitial>>>> = OnceLock::new();
I.get_or_init(|| Mutex::new((0..keypath::VALUE_SLOTS).map(|_| None).collect()))
}
fn key_path_for<T: crate::views::KeyValue>(name: &str, initial: &T) -> u64 {
let mut names = names().lock().expect("observable property names");
if let Some(n) = names.iter().find(|n| n.name == name) {
let (key_path, declared) = (n.key_path, n.value_type);
drop(names);
assert_eq!(
declared,
core::any::TypeId::of::<T>(),
"the observable property `{name}` was already declared with a different \
type; one name is one key path, so one name is one type",
);
return key_path;
}
let slot = keypath::next_value_slot();
let initial = initial.clone();
initials().lock().expect("observable property initials")[slot] =
Some(Box::new(move |out| unsafe { initial.write(out) }));
unsafe {
let (get, set, get_target, set_target) = keypath::value_thunks(slot);
*get_target = identity_get as *const c_void;
*set_target = identity_set as *const c_void;
let key_path = keypath::build_writable_computed(
keypath::value_slot(slot),
b"So14RustObservableC",
T::mangled().as_bytes(),
get,
set,
);
swift_runtime_sys::RuntimeRaw::swift_retain(key_path as *mut c_void);
names.push(Named {
name: name.to_string(),
value_type: core::any::TypeId::of::<T>(),
key_path,
});
key_path
}
}
unsafe extern "C" fn identity_get(out: *mut u8, _root: *const c_void, slot: u64) {
let initials = initials().lock().expect("observable property initials");
if let Some(write) = initials[slot as usize].as_ref() {
write(out);
}
}
unsafe extern "C" fn identity_set(_value: *const u8, _root: *mut c_void, _slot: u64) {}
type ErasedStorage = Arc<dyn core::any::Any + Send + Sync>;
#[derive(Clone)]
pub struct Model {
object: usize,
registrar: Arc<Mutex<Vec<u8>>>,
properties: Arc<Mutex<Vec<(String, ErasedStorage)>>>,
}
unsafe impl Send for Model {}
unsafe impl Sync for Model {}
pub fn object() -> Model {
model_of(None)
}
pub fn model(initial: i64) -> Model {
model_of(Some(initial))
}
fn model_of(initial: Option<i64>) -> Model {
let kind = kind();
unsafe {
let object = objc::send(
objc::send(kind.class as *mut c_void, objc::sel(c"alloc")),
objc::sel(c"init"),
);
assert!(!object.is_null(), "RustObservable init");
let meta = abi::metadata0(resolve::observation_registrar_ma());
assert!(!meta.is_null(), "ObservationRegistrar metadata");
let mut registrar = vec![0u8; abi::type_bytes(meta)];
core::arch::asm!("blr {f}", f = in(reg) resolve::observation_registrar_init(),
in("x8") registrar.as_mut_ptr(),
lateout("x0") _, lateout("x1") _, lateout("x2") _, 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("x20") _, lateout("x21") _,
clobber_abi("C"),
);
let model = Model {
object: object as usize,
registrar: Arc::new(Mutex::new(registrar)),
properties: Arc::new(Mutex::new(Vec::new())),
};
if let Some(initial) = initial {
let _ = model.property(DEFAULT_PROPERTY, initial);
}
model
}
}
const DEFAULT_PROPERTY: &str = "value";
impl Model {
pub fn as_object(&self) -> *mut c_void {
self.object as *mut c_void
}
pub fn conformance(&self) -> (*const c_void, *const c_void) {
let k = kind();
(k.metadata as *const c_void, k.observable_wt as *const c_void)
}
pub fn property<T: crate::views::KeyValue>(
&self,
name: &str,
initial: impl crate::views::IntoKeyValue<T>,
) -> Property<T> {
let initial = initial.into_key_value();
let key_path = key_path_for(name, &initial);
let mut properties = self.properties.lock().expect("model properties");
let existing = properties.iter().find(|(n, _)| n == name).map(|(_, s)| s.clone());
let storage = match existing {
Some(erased) => {
drop(properties);
erased.downcast::<Mutex<T>>().unwrap_or_else(|_| {
panic!("the property `{name}` on this model does not hold this type")
})
}
None => {
let storage = Arc::new(Mutex::new(initial));
properties.push((name.to_string(), storage.clone()));
storage
}
};
Property {
object: self.object,
registrar: self.registrar.clone(),
key_path,
value: storage,
}
}
pub fn get(&self) -> i64 {
self.property(DEFAULT_PROPERTY, 0).get()
}
pub fn set(&self, value: i64) {
self.property(DEFAULT_PROPERTY, 0).set(value);
}
pub fn peek(&self) -> i64 {
self.property(DEFAULT_PROPERTY, 0).peek()
}
}
#[derive(Clone)]
pub struct Property<T> {
object: usize,
registrar: Arc<Mutex<Vec<u8>>>,
key_path: u64,
value: Arc<Mutex<T>>,
}
impl<T: Clone> Property<T> {
pub fn get(&self) -> T {
unsafe { self.registrar_call(resolve::registrar_access()) };
self.value.lock().expect("property value").clone()
}
pub fn set(&self, value: T) {
unsafe {
self.registrar_call(resolve::registrar_will_set());
*self.value.lock().expect("property value") = value;
self.registrar_call(resolve::registrar_did_set());
}
}
pub fn peek(&self) -> T {
self.value.lock().expect("property value").clone()
}
unsafe fn registrar_call(&self, f: *const c_void) {
let k = kind();
let mut subject = self.object;
let mut registrar = self.registrar.lock().expect("registrar");
unsafe {
core::arch::asm!("blr {f}", f = in(reg) f,
in("x0") &mut subject as *mut usize,
in("x1") self.key_path,
in("x2") k.observable_wt,
in("x20") registrar.as_mut_ptr(),
lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
lateout("x7") _, lateout("x8") _,
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"),
);
}
}
}
struct Reading<'a> {
read: &'a mut dyn FnMut(),
}
unsafe extern "C" fn run_reading(_out: *mut u8, payload: *mut c_void) {
let r = unsafe { &mut *(payload as *mut Reading<'_>) };
(r.read)();
}
core::arch::global_asm!(
".p2align 2",
".globl _rswift_obs_onchange_thunk",
"_rswift_obs_onchange_thunk:",
"mov x0, x20",
"b _rswift_obs_onchange",
);
unsafe extern "C" {
#[link_name = "rswift_obs_onchange_thunk"]
fn onchange_thunk();
}
#[repr(C)]
struct Thick {
function: u64,
context: u64,
}
#[repr(C)]
struct OnChange {
isa: *const c_void,
refcount: u64,
handler: Thick,
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rswift_obs_onchange(ctx: *mut c_void) -> Thick {
let c = unsafe { &*(ctx as *const OnChange) };
Thick { function: c.handler.function, context: c.handler.context }
}
pub fn track(read: impl FnMut(), on_change: impl Fn() + Send + Sync + 'static) {
let mut read = read;
let mut reading = Reading { read: &mut read };
unsafe {
let mut ctx = abi::ClosureCtx::new(
run_reading,
&mut reading as *mut Reading<'_> as *mut c_void,
);
let handler = crate::views::action_words(on_change);
let onchange = Box::leak(Box::new(OnChange {
isa: abi::immortal_isa(),
refcount: abi::IMMORTAL_REFCOUNT,
handler: Thick { function: handler[0], context: handler[1] },
})) as *mut OnChange;
let unit = abi::resolve_type_by_mangled_name(b"yt");
assert!(!unit.is_null(), "() metadata");
let mut out = [0u8; 8];
core::arch::asm!("blr {f}", f = in(reg) resolve::with_observation_tracking(),
in("x0") abi::swift_closure_thunk as *const c_void,
in("x1") &mut ctx as *mut abi::ClosureCtx,
in("x2") onchange_thunk as *const c_void,
in("x3") onchange,
in("x4") unit,
in("x8") out.as_mut_ptr(),
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("x20") _, lateout("x21") _,
clobber_abi("C"),
);
}
}
pub fn environment_model(view: &ViewHandle, model: &Model) -> ViewHandle {
unsafe { publish(view, model, resolve::environment_observable_fn(), resolve::environment_observable_oret()) }
}
pub fn focused_value_model(view: &ViewHandle, model: &Model) -> ViewHandle {
unsafe { publish(view, model, resolve::focused_value_fn(), resolve::focused_value_oret()) }
}
unsafe fn publish(
view: &ViewHandle,
model: &Model,
f: *const c_void,
oret: *const c_void,
) -> ViewHandle {
let (meta, wt) = model.conformance();
unsafe {
crate::views::generic1_modifier_ints(
f,
oret,
view,
meta,
wt,
&[model.as_object() as u64],
[0.0; 4],
)
}
}