Define data types for ID-based resource handling (#4168)

* Add resource lib

* rename ResourceSource to ResourceInput

* Rename ResourceInput -> DataSource and improve API

* Review

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Timon
2026-05-25 10:50:57 +00:00
committed by GitHub
parent d0d9a350c7
commit bbbe04903f
28 changed files with 187 additions and 46 deletions

View File

@@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
[features]
default = ["serde"]
serde = ["dep:serde", "core-types/serde", "vector-types/serde", "text-nodes/serde"]
serde = ["dep:serde", "core-types/serde", "vector-types/serde", "text-nodes/serde", "graphene-resource/serde"]
wasm = ["dep:web-sys"]
wgpu = ["dep:wgpu"]
@@ -18,6 +18,7 @@ dyn-any = { workspace = true }
core-types = { workspace = true }
vector-types = { workspace = true }
text-nodes = { workspace = true }
graphene-resource = { workspace = true }
# Workspace dependencies
blake3 = { workspace = true }

View File

@@ -9,9 +9,10 @@ use std::time::Duration;
use text_nodes::FontCache;
use vector_types::vector::style::RenderMode;
pub mod resource;
pub use core_types::resource::Resource;
pub use resource::{LoadResource, ResourceFuture, ResourceHash, ResourceStorage};
pub mod resource {
pub use core_types::resource::*;
pub use graphene_resource::*;
}
#[cfg(feature = "wgpu")]
#[derive(Debug, Clone, Hash, PartialEq, Eq, DynAny)]
@@ -49,7 +50,7 @@ pub trait ApplicationIo {
fn gpu_executor(&self) -> Option<&Self::Executor> {
None
}
fn load_resource(&self, hash: ResourceHash) -> resource::ResourceFuture;
fn load_resource(&self, hash: resource::ResourceHash) -> resource::ResourceFuture;
}
impl<T: ApplicationIo> ApplicationIo for &T {
@@ -59,7 +60,7 @@ impl<T: ApplicationIo> ApplicationIo for &T {
(**self).gpu_executor()
}
fn load_resource(&self, hash: ResourceHash) -> resource::ResourceFuture {
fn load_resource(&self, hash: resource::ResourceHash) -> resource::ResourceFuture {
(**self).load_resource(hash)
}
}

View File

@@ -1,168 +0,0 @@
use core_types::CacheHash;
use core_types::resource::Resource;
use dyn_any::DynAny;
use std::fmt;
use std::future::Future;
use std::hash::Hash;
use std::pin::Pin;
pub trait LoadResource: Send + Sync {
fn load(&self, hash: ResourceHash) -> ResourceFuture;
}
pub type ResourceFuture = Pin<Box<dyn Future<Output = Option<Resource>> + Send + 'static>>;
pub trait ResourceStorage: LoadResource {
fn store(&mut self, data: &[u8]) -> ResourceHash;
fn contains(&mut self, hash: &ResourceHash) -> bool;
fn garbage_collect(&mut self, used: &[ResourceHash]);
}
/// Blake3 content hash of a resource, represented as 32 bytes
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord, DynAny)]
pub struct ResourceHash([u8; 32]);
impl From<&[u8]> for ResourceHash {
fn from(data: &[u8]) -> Self {
Self(blake3::hash(data).into())
}
}
impl From<[u8; 32]> for ResourceHash {
fn from(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
impl From<&ResourceHash> for [u8; 32] {
fn from(hash: &ResourceHash) -> Self {
hash.0
}
}
impl From<&ResourceHash> for String {
fn from(hash: &ResourceHash) -> Self {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(hash.0.len() * 2);
for byte in &hash.0 {
out.push(HEX[(byte >> 4) as usize] as char);
out.push(HEX[(byte & 0x0f) as usize] as char);
}
out
}
}
impl fmt::Display for ResourceHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&String::from(self))
}
}
impl std::str::FromStr for ResourceHash {
type Err = ResourceHashParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
fn decode_hex_nibble(byte: u8, position: usize) -> Result<u8, ResourceHashParseError> {
match byte {
b'0'..=b'9' => Ok(byte - b'0'),
b'a'..=b'f' => Ok(byte - b'a' + 10),
b'A'..=b'F' => Ok(byte - b'A' + 10),
_ => Err(ResourceHashParseError::InvalidCharacter { byte, position }),
}
}
let bytes = s.as_bytes();
if bytes.len() != 64 {
return Err(ResourceHashParseError::InvalidLength { found: bytes.len() });
}
let mut out = [0u8; 32];
for (index, chunk) in bytes.chunks_exact(2).enumerate() {
let high = decode_hex_nibble(chunk[0], index * 2)?;
let low = decode_hex_nibble(chunk[1], index * 2 + 1)?;
out[index] = (high << 4) | low;
}
Ok(Self(out))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResourceHashParseError {
InvalidLength { found: usize },
InvalidCharacter { byte: u8, position: usize },
}
impl fmt::Display for ResourceHashParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength { found } => write!(f, "resource hash must be 64 hex characters, got {found}"),
Self::InvalidCharacter { byte, position } => write!(f, "resource hash contains non-hex byte {byte:#04x} at position {position}"),
}
}
}
impl std::error::Error for ResourceHashParseError {}
impl TryFrom<&str> for ResourceHash {
type Error = ResourceHashParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
value.parse()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for ResourceHash {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
serializer.serialize_str(&String::from(self))
} else {
serializer.serialize_bytes(&self.0)
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for ResourceHash {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct ResourceHashVisitor;
impl<'de> serde::de::Visitor<'de> for ResourceHashVisitor {
type Value = ResourceHash;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a 64-character hex string or 32 raw bytes")
}
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
ResourceHash::try_from(value).map_err(E::custom)
}
fn visit_bytes<E: serde::de::Error>(self, value: &[u8]) -> Result<Self::Value, E> {
let bytes: [u8; 32] = value.try_into().map_err(|_| E::invalid_length(value.len(), &"32 bytes"))?;
Ok(ResourceHash(bytes))
}
fn visit_seq<A: serde::de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let mut bytes = [0u8; 32];
for (i, slot) in bytes.iter_mut().enumerate() {
*slot = seq.next_element()?.ok_or_else(|| serde::de::Error::invalid_length(i, &"32 bytes"))?;
}
Ok(ResourceHash(bytes))
}
}
if deserializer.is_human_readable() {
deserializer.deserialize_str(ResourceHashVisitor)
} else {
deserializer.deserialize_bytes(ResourceHashVisitor)
}
}
}
impl CacheHash for ResourceHash {
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(self, state);
}
}