2021-02-07 20:25:56 -08:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::ffi::CString;
|
|
|
|
use std::sync::{Arc, RwLock};
|
|
|
|
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
|
|
|
|
use objc::declare::ClassDecl;
|
|
|
|
use objc::runtime::{objc_getClass, Class};
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
static ref CLASSES: ClassMap = ClassMap::new();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A ClassMap is a general cache for our Objective-C class lookup and registration. Rather than
|
|
|
|
/// constantly calling into the runtime, we store pointers to Class types here after first lookup
|
|
|
|
/// and/or creation. The general store format is (roughly speaking) as follows:
|
|
|
|
///
|
2021-03-04 17:24:39 -08:00
|
|
|
/// ```no_run
|
2021-02-07 20:25:56 -08:00
|
|
|
/// {
|
|
|
|
/// "subclass_type": {
|
|
|
|
/// "superclass_type": *const Class as usize
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// The reasoning behind the double map is that it allows for lookup without allocating a `String`
|
|
|
|
/// on each hit; allocations are only required when creating a Class to inject, purely for naming
|
|
|
|
/// and debugging reasons.
|
|
|
|
///
|
|
|
|
/// There may be a way to do this without using HashMaps and avoiding the heap, but working and
|
|
|
|
/// usable beats ideal for now. Open to suggestions.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub(crate) struct ClassMap(RwLock<HashMap<&'static str, HashMap<&'static str, usize>>>);
|
|
|
|
|
|
|
|
impl ClassMap {
|
|
|
|
/// Returns a new ClassMap.
|
|
|
|
pub fn new() -> Self {
|
|
|
|
ClassMap(RwLock::new({
|
|
|
|
let mut map = HashMap::new();
|
|
|
|
|
|
|
|
// Top-level classes, like `NSView`, we cache here. The reasoning is that if a subclass
|
2021-03-04 17:24:39 -08:00
|
|
|
// is being created, we can avoid querying the runtime for the superclass - i.e, many
|
|
|
|
// subclasses will have `NSView` as their superclass.
|
2021-02-07 20:25:56 -08:00
|
|
|
map.insert("_supers", HashMap::new());
|
|
|
|
|
|
|
|
map
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempts to load a previously registered subclass.
|
2021-03-04 17:24:39 -08:00
|
|
|
pub fn load_subclass(
|
|
|
|
&self,
|
|
|
|
subclass_name: &'static str,
|
|
|
|
superclass_name: &'static str
|
|
|
|
) -> Option<*const Class> {
|
2021-02-07 20:25:56 -08:00
|
|
|
let reader = self.0.read().unwrap();
|
|
|
|
|
|
|
|
if let Some(inner) = (*reader).get(subclass_name) {
|
|
|
|
if let Some(class) = inner.get(superclass_name) {
|
|
|
|
return Some(*class as *const Class);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Store a newly created subclass type.
|
|
|
|
pub fn store_subclass(
|
|
|
|
&self,
|
|
|
|
subclass_name: &'static str,
|
|
|
|
superclass_name: &'static str,
|
|
|
|
class: *const Class,
|
|
|
|
) {
|
|
|
|
let mut writer = self.0.write().unwrap();
|
|
|
|
|
|
|
|
if let Some(map) = (*writer).get_mut(subclass_name) {
|
|
|
|
map.insert(superclass_name, class as usize);
|
|
|
|
} else {
|
|
|
|
let mut map = HashMap::new();
|
|
|
|
map.insert(superclass_name, class as usize);
|
|
|
|
(*writer).insert(subclass_name, map);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempts to load a Superclass. This first checks for the cached pointer; if not present, it
|
|
|
|
/// will load the superclass from the Objective-C runtime and cache it for future lookup. This
|
|
|
|
/// assumes that the class is one that should *already* and *always* exist in the runtime, and
|
|
|
|
/// by design will panic if it can't load the correct superclass, as that would lead to very
|
|
|
|
/// invalid behavior.
|
|
|
|
pub fn load_superclass(&self, name: &'static str) -> Option<*const Class> {
|
|
|
|
{
|
|
|
|
let reader = self.0.read().unwrap();
|
|
|
|
if let Some(superclass) = (*reader)["_supers"].get(name) {
|
|
|
|
return Some(*superclass as *const Class);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let objc_superclass_name = CString::new(name).unwrap();
|
|
|
|
let superclass = unsafe {
|
|
|
|
objc_getClass(objc_superclass_name.as_ptr() as *const _)
|
|
|
|
};
|
|
|
|
|
|
|
|
// This should not happen, for our use-cases, but it's conceivable that this could actually
|
|
|
|
// be expected, so just return None and let the caller panic if so desired.
|
|
|
|
if superclass.is_null() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut writer = self.0.write().unwrap();
|
|
|
|
if let Some(supers) = (*writer).get_mut("_supers") {
|
|
|
|
supers.insert(name, superclass as usize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Some(superclass)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempts to load a subclass, given a `superclass_name` and subclass_name. If
|
|
|
|
/// the subclass cannot be loaded, it's dynamically created and injected into
|
|
|
|
/// the runtime, and then returned. The returned value can be used for allocating new instances of
|
|
|
|
/// this class in the Objective-C runtime.
|
|
|
|
///
|
|
|
|
/// The `config` block can be used to customize the Class declaration before it's registered with
|
|
|
|
/// the runtime. This is useful for adding method handlers and ivar storage.
|
|
|
|
///
|
|
|
|
/// If the superclass cannot be loaded, this will panic. If the subclass cannot be
|
|
|
|
/// created, this will panic. In general, this is expected to work, and if it doesn't,
|
|
|
|
/// the entire framework will not really work.
|
|
|
|
///
|
|
|
|
/// There's definitely room to optimize here, but it works for now.
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn load_or_register_class<F>(
|
|
|
|
superclass_name: &'static str,
|
|
|
|
subclass_name: &'static str,
|
|
|
|
config: F
|
|
|
|
) -> *const Class
|
|
|
|
where
|
|
|
|
F: Fn(&mut ClassDecl) + 'static
|
|
|
|
{
|
|
|
|
if let Some(subclass) = CLASSES.load_subclass(subclass_name, superclass_name) {
|
|
|
|
return subclass;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(superclass) = CLASSES.load_superclass(superclass_name) {
|
|
|
|
let objc_subclass_name = format!("{}_{}", subclass_name, superclass_name);
|
|
|
|
|
|
|
|
match ClassDecl::new(&objc_subclass_name, unsafe { &*superclass }) {
|
|
|
|
Some(mut decl) => {
|
|
|
|
config(&mut decl);
|
|
|
|
|
|
|
|
let class = decl.register();
|
|
|
|
CLASSES.store_subclass(subclass_name, superclass_name, class);
|
|
|
|
return class;
|
|
|
|
},
|
|
|
|
|
|
|
|
None => {
|
2021-03-04 17:24:39 -08:00
|
|
|
panic!(
|
|
|
|
"Subclass of type {}_{} could not be allocated.",
|
|
|
|
subclass_name,
|
|
|
|
superclass_name
|
|
|
|
);
|
2021-02-07 20:25:56 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
panic!(
|
|
|
|
"Attempted to create subclass for {}, but unable to load superclass of type {}.",
|
|
|
|
subclass_name,
|
|
|
|
superclass_name
|
|
|
|
);
|
|
|
|
}
|