use core::sync::atomic::{AtomicUsize, Ordering};
pub(crate) type ResolveFn = fn(&str) -> u32;
pub(crate) type HandleResolveFn = fn(&str) -> Option<u32>;
struct NameResolverSlot(AtomicUsize);
impl NameResolverSlot {
const fn new() -> Self {
Self(AtomicUsize::new(0))
}
fn set(&self, f: ResolveFn) {
self.0.store(f as usize, Ordering::Release);
}
fn resolve(&self, name: &str) -> Option<u32> {
let v = self.0.load(Ordering::Acquire);
if v == 0 {
return None;
}
let f: ResolveFn = unsafe { core::mem::transmute::<usize, ResolveFn>(v) };
Some(f(name))
}
}
struct HandleResolverSlot(AtomicUsize);
impl HandleResolverSlot {
const fn new() -> Self {
Self(AtomicUsize::new(0))
}
fn set(&self, f: HandleResolveFn) {
self.0.store(f as usize, Ordering::Release);
}
fn resolve(&self, name: &str) -> Option<u32> {
let v = self.0.load(Ordering::Acquire);
if v == 0 {
return None;
}
let f: HandleResolveFn = unsafe { core::mem::transmute::<usize, HandleResolveFn>(v) };
f(name)
}
}
#[cfg(not(test))]
static RESOLVER: NameResolverSlot = NameResolverSlot::new();
#[cfg(not(test))]
pub fn set_name_resolver(f: ResolveFn) {
RESOLVER.set(f);
}
#[cfg(not(test))]
pub(crate) fn resolve_name(name: &str) -> Option<u32> {
RESOLVER.resolve(name)
}
#[cfg(test)]
std::thread_local! {
static RESOLVER: core::cell::Cell<Option<ResolveFn>> =
const { core::cell::Cell::new(None) };
}
#[cfg(test)]
pub fn set_name_resolver(f: ResolveFn) {
RESOLVER.with(|slot| slot.set(Some(f)));
}
#[cfg(test)]
pub(crate) fn resolve_name(name: &str) -> Option<u32> {
RESOLVER.with(|slot| slot.get()).map(|f| f(name))
}
macro_rules! handle_resolver {
(
$(#[$extra:meta])*
$slot:ident, $noun:literal, $set_fn:ident, $resolve_fn:ident $(,)?
) => {
#[cfg(not(test))]
static $slot: HandleResolverSlot = HandleResolverSlot::new();
#[cfg(test)]
std::thread_local! {
static $slot: core::cell::Cell<Option<HandleResolveFn>> =
const { core::cell::Cell::new(None) };
}
#[doc = concat!(
"Install the name -> ", $noun,
"-handle resolver. Called by concinnity-cook, backed by the current ",
"build's declaration-ordered ", $noun,
" handle map. Idempotent; the last writer wins."
)]
$(#[$extra])*
#[cfg(not(test))]
pub fn $set_fn(f: HandleResolveFn) {
$slot.set(f);
}
#[cfg(test)]
#[doc = concat!("Install the name -> ", $noun, "-handle resolver.")]
pub fn $set_fn(f: HandleResolveFn) {
$slot.with(|slot| slot.set(Some(f)));
}
#[doc = concat!(
"Resolve a ", $noun,
" reference name to its dense handle value via the installed ",
"resolver. `None` means either no resolver is installed or the name ",
"is not a declared ", $noun,
"; the caller decides whether to fall back (a validation context) ",
"or to fail (a real build)."
)]
#[cfg(not(test))]
pub(crate) fn $resolve_fn(name: &str) -> Option<u32> {
$slot.resolve(name)
}
#[cfg(test)]
pub(crate) fn $resolve_fn(name: &str) -> Option<u32> {
$slot.with(|slot| slot.get()).and_then(|f| f(name))
}
};
}
handle_resolver! {
TEXTURE_HANDLE_RESOLVER, "texture",
set_texture_handle_resolver, resolve_texture_handle,
}
handle_resolver! {
AUDIO_CLIP_HANDLE_RESOLVER, "audio-clip",
set_audio_clip_handle_resolver, resolve_audio_clip_handle,
}
handle_resolver! {
FONT_HANDLE_RESOLVER, "font",
set_font_handle_resolver, resolve_font_handle,
}
handle_resolver! {
MESH_HANDLE_RESOLVER, "mesh",
set_mesh_handle_resolver, resolve_mesh_handle,
}
handle_resolver! {
MATERIAL_HANDLE_RESOLVER, "material",
set_material_handle_resolver, resolve_material_handle,
}
handle_resolver! {
SKINNED_MESH_HANDLE_RESOLVER, "skinned-mesh",
set_skinned_mesh_handle_resolver, resolve_skinned_mesh_handle,
}
handle_resolver! {
SHADER_HANDLE_RESOLVER, "shader",
set_shader_handle_resolver, resolve_shader_handle,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ecs::asset_id::{AssetId, AssetRef, de_opt_asset_ref, de_opt_asset_ref_typed};
use crate::test_support::{install_resolvers, len_handle_resolver, len_name_resolver};
struct Clip;
#[test]
fn a_slot_reads_back_the_function_pointer_it_was_given() {
let name_slot = NameResolverSlot::new();
assert_eq!(name_slot.resolve("floor"), None);
name_slot.set(len_name_resolver);
assert_eq!(name_slot.resolve("floor"), Some(5));
let handle_slot = HandleResolverSlot::new();
assert_eq!(handle_slot.resolve("floor"), None);
handle_slot.set(len_handle_resolver);
assert_eq!(handle_slot.resolve("floor"), Some(5));
assert_eq!(handle_slot.resolve("unknown_x"), None);
}
#[test]
fn installed_resolver_is_used() {
set_name_resolver(len_name_resolver);
assert_eq!(resolve_name("abcd"), Some(4));
}
#[test]
fn asset_id_resolves_a_name_through_the_seam() {
set_name_resolver(len_name_resolver);
let id: AssetId = serde_json::from_str("\"floor\"").unwrap();
assert_eq!(id, AssetId(5));
}
#[test]
fn asset_ref_resolves_a_name_through_the_seam() {
set_name_resolver(len_name_resolver);
let r: AssetRef<Clip> = serde_json::from_str("\"wall\"").unwrap();
assert_eq!(r.id(), Some(AssetId(4)));
assert!(r.is_resolved());
}
#[test]
fn every_handle_seam_resolves_through_its_own_slot() {
install_resolvers();
set_texture_handle_resolver(len_handle_resolver);
set_audio_clip_handle_resolver(len_handle_resolver);
set_font_handle_resolver(len_handle_resolver);
set_mesh_handle_resolver(len_handle_resolver);
set_material_handle_resolver(len_handle_resolver);
set_skinned_mesh_handle_resolver(len_handle_resolver);
set_shader_handle_resolver(len_handle_resolver);
assert_eq!(resolve_texture_handle("floor"), Some(5));
assert_eq!(resolve_audio_clip_handle("floor"), Some(5));
assert_eq!(resolve_font_handle("floor"), Some(5));
assert_eq!(resolve_mesh_handle("floor"), Some(5));
assert_eq!(resolve_material_handle("floor"), Some(5));
assert_eq!(resolve_skinned_mesh_handle("floor"), Some(5));
assert_eq!(resolve_shader_handle("floor"), Some(5));
assert_eq!(resolve_texture_handle("unknown_x"), None);
assert_eq!(resolve_shader_handle("unknown_x"), None);
}
#[test]
fn opt_helpers_resolve_a_name_and_pass_through_an_id() {
set_name_resolver(len_name_resolver);
#[derive(serde::Deserialize)]
struct Bare {
#[serde(default, deserialize_with = "de_opt_asset_ref")]
r: Option<AssetId>,
}
#[derive(serde::Deserialize)]
struct Typed {
#[serde(default, deserialize_with = "de_opt_asset_ref_typed")]
r: Option<AssetRef<Clip>>,
}
assert_eq!(
serde_json::from_str::<Bare>("{\"r\":\"mesh_a\"}")
.unwrap()
.r,
Some(AssetId(6))
);
assert_eq!(
serde_json::from_str::<Typed>("{\"r\":\"abc\"}")
.unwrap()
.r
.unwrap()
.id(),
Some(AssetId(3))
);
}
}