Make Resource aware of its own hash (#4187)

* Fix future message handler field name

* Add Resource::hash()

* Move all remaining resource types from core-types to graphene-resources

* Review
This commit is contained in:
Timon
2026-05-31 23:08:01 +00:00
committed by GitHub
parent 78679a5ba2
commit e32ff4b7f0
11 changed files with 96 additions and 92 deletions

1
Cargo.lock generated
View File

@@ -4361,6 +4361,7 @@ dependencies = [
"futures",
"glam",
"graphene-hash",
"graphene-resource",
"image",
"kurbo",
"ndarray",

View File

@@ -15,7 +15,7 @@ impl Editor {
graphene_std::uuid::set_uuid_seed(uuid_random_seed);
let mut dispatcher = Dispatcher::new(resource_storage);
dispatcher.message_handlers.async_message_handler.set_wake(wake);
dispatcher.message_handlers.future_message_handler.set_wake(wake);
application_io.inject_resource_proxy(dispatcher.message_handlers.resource_storage_message_handler.resources());
crate::node_graph_executor::replace_application_io(application_io);

View File

@@ -20,7 +20,7 @@ pub struct Dispatcher {
pub struct DispatcherMessageHandlers {
animation_message_handler: AnimationMessageHandler,
app_window_message_handler: AppWindowMessageHandler,
pub(crate) async_message_handler: FutureMessageHandler,
pub(crate) future_message_handler: FutureMessageHandler,
broadcast_message_handler: BroadcastMessageHandler,
clipboard_message_handler: ClipboardMessageHandler,
color_picker_message_handler: ColorPickerMessageHandler,
@@ -127,7 +127,7 @@ impl Dispatcher {
// Drain async results into the queue before processing the new message.
let mut async_results = VecDeque::new();
self.message_handlers.async_message_handler.drain_results(&mut async_results);
self.message_handlers.future_message_handler.drain_results(&mut async_results);
if !async_results.is_empty() {
Self::schedule_execution(&mut self.message_queues, true, async_results);
}
@@ -182,7 +182,7 @@ impl Dispatcher {
self.message_handlers.app_window_message_handler.process_message(message, &mut queue, ());
}
Message::Future(message) => {
self.message_handlers.async_message_handler.process_message(message, &mut queue, FutureMessageContext {});
self.message_handlers.future_message_handler.process_message(message, &mut queue, FutureMessageContext {});
}
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
Message::Clipboard(message) => self.message_handlers.clipboard_message_handler.process_message(message, &mut queue, ()),

View File

@@ -53,7 +53,7 @@ impl MmapResourceStorage {
let path = self.path_for(hash);
let mmap = Self::open_mmap(&path)?;
let resource = Resource::new(MmappedBytes(mmap));
let resource = Resource::new_unchecked(MmappedBytes(mmap), *hash);
self.cache.write().unwrap_or_else(|poisoned| poisoned.into_inner()).insert(*hash, resource.clone());
Some(resource)

View File

@@ -81,7 +81,7 @@ impl ResourceStorage for OpfsResourceStorage {
let mut bytes = None;
if !guard.cache.contains_key(&hash) {
let resource_bytes = Arc::<[u8]>::from(data);
guard.cache.insert(hash, Resource::new(resource_bytes.clone()));
guard.cache.insert(hash, Resource::new_unchecked(resource_bytes.clone(), hash));
bytes = Some(resource_bytes);
}
@@ -302,7 +302,7 @@ async fn read_from_opfs(inner: Arc<Mutex<Inner>>, hash: ResourceHash) -> Option<
return None;
}
let resource = Resource::new(bytes);
let resource = Resource::new_unchecked(bytes, hash);
let mut guard = inner.lock().unwrap();
if let Some(resource) = guard.cache.get(&hash) {
return Some(resource.clone());

View File

@@ -9,10 +9,7 @@ use std::time::Duration;
use text_nodes::FontCache;
use vector_types::vector::style::RenderMode;
pub mod resource {
pub use core_types::resource::*;
pub use graphene_resource::*;
}
pub use graphene_resource as resource;
#[cfg(feature = "wgpu")]
#[derive(Debug, Clone, Hash, PartialEq, Eq, DynAny)]

View File

@@ -11,7 +11,6 @@ pub mod misc;
pub mod ops;
pub mod registry;
pub mod render_complexity;
pub mod resource;
pub mod transform;
pub mod uuid;
pub mod value;

View File

@@ -1,54 +0,0 @@
use dyn_any::DynAny;
use graphene_hash::CacheHash;
use std::hash::Hash;
use std::ops::Deref;
use std::sync::Arc;
#[derive(Clone, DynAny)]
pub struct Resource {
inner: Arc<dyn AsRef<[u8]> + Send + Sync>,
}
impl Resource {
pub fn new<T: AsRef<[u8]> + Send + Sync + 'static>(data: T) -> Self {
Self { inner: Arc::new(data) }
}
}
impl From<&Resource> for Arc<dyn AsRef<[u8]> + Send + Sync> {
fn from(val: &Resource) -> Self {
val.inner.clone()
}
}
impl Deref for Resource {
type Target = [u8];
fn deref(&self) -> &[u8] {
(*self.inner).as_ref()
}
}
impl AsRef<[u8]> for Resource {
fn as_ref(&self) -> &[u8] {
(*self.inner).as_ref()
}
}
impl std::fmt::Debug for Resource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Resource").field("len", &self.len()).finish()
}
}
impl PartialEq for Resource {
fn eq(&self, other: &Self) -> bool {
self.as_ref() == other.as_ref()
}
}
impl CacheHash for Resource {
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_ref().hash(state);
}
}

View File

@@ -1,38 +1,68 @@
use core_types::resource::Resource;
use core_types::{CacheHash, graphene_hash};
use dyn_any::DynAny;
use std::collections::HashMap;
use std::fmt;
use std::future::Future;
use std::hash::Hash;
use std::ops::Deref;
use std::pin::Pin;
use std::sync::Arc;
pub trait LoadResource: Send + Sync {
fn load(&self, hash: ResourceHash) -> ResourceFuture;
#[derive(Clone, DynAny)]
pub struct Resource {
inner: Arc<dyn AsRef<[u8]> + Send + Sync>,
hash: ResourceHash,
}
pub type ResourceFuture = Pin<Box<dyn Future<Output = Option<Resource>> + Send + 'static>>;
impl Resource {
pub fn new<T: AsRef<[u8]> + Send + Sync + 'static>(data: T) -> Self {
let hash = ResourceHash::from(data.as_ref());
Self { inner: Arc::new(data), hash }
}
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]);
}
pub fn new_unchecked<T: AsRef<[u8]> + Send + Sync + 'static>(data: T, hash: ResourceHash) -> Self {
Self { inner: Arc::new(data), hash }
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, graphene_hash::CacheHash, PartialOrd, Ord, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ResourceId(u64);
impl ResourceId {
pub fn new() -> Self {
Self(core_types::uuid::generate_uuid())
pub fn hash(&self) -> ResourceHash {
self.hash
}
}
impl std::fmt::Display for ResourceId {
impl From<&Resource> for Arc<dyn AsRef<[u8]> + Send + Sync> {
fn from(val: &Resource) -> Self {
val.inner.clone()
}
}
impl Deref for Resource {
type Target = [u8];
fn deref(&self) -> &[u8] {
(*self.inner).as_ref()
}
}
impl AsRef<[u8]> for Resource {
fn as_ref(&self) -> &[u8] {
(*self.inner).as_ref()
}
}
impl std::fmt::Debug for Resource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
f.debug_struct("Resource").field("len", &self.len()).finish()
}
}
impl PartialEq for Resource {
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash
}
}
impl CacheHash for Resource {
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.hash.cache_hash(state);
}
}
@@ -70,8 +100,8 @@ impl From<&ResourceHash> for String {
}
}
impl fmt::Display for ResourceHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
impl std::fmt::Display for ResourceHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&String::from(self))
}
}
@@ -111,8 +141,8 @@ pub enum ResourceHashParseError {
InvalidCharacter { byte: u8, position: usize },
}
impl fmt::Display for ResourceHashParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
impl std::fmt::Display for ResourceHashParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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}"),
@@ -149,7 +179,7 @@ impl<'de> serde::Deserialize<'de> for ResourceHash {
impl<'de> serde::de::Visitor<'de> for ResourceHashVisitor {
type Value = ResourceHash;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a 64-character hex string or 32 raw bytes")
}
@@ -185,6 +215,35 @@ impl CacheHash for ResourceHash {
}
}
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]);
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, graphene_hash::CacheHash, PartialOrd, Ord, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ResourceId(u64);
impl ResourceId {
pub fn new() -> Self {
Self(core_types::uuid::generate_uuid())
}
}
impl std::fmt::Display for ResourceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
pub type DataSources = Box<[DataSource]>;
#[derive(Clone, Debug, PartialEq, Eq)]

View File

@@ -17,6 +17,7 @@ std = [
"serde",
"dep:core-types",
"dep:dyn-any",
"dep:graphene-resource",
"dep:graphene-hash",
"dep:raster-types",
"dep:vector-types",
@@ -43,6 +44,7 @@ node-macro = { workspace = true }
# Local std dependencies
dyn-any = { workspace = true, optional = true }
core-types = { workspace = true, optional = true }
graphene-resource = { workspace = true, optional = true }
graphene-hash = { workspace = true, optional = true }
raster-types = { workspace = true, optional = true }
vector-types = { workspace = true, optional = true }

View File

@@ -5,11 +5,11 @@ use core_types::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBM
use core_types::context::{Ctx, ExtractFootprint};
use core_types::list::{Item, List};
use core_types::math::bbox::Bbox;
use core_types::resource::Resource;
use core_types::transform::Transform;
use dyn_any::DynAny;
use fastnoise_lite;
use glam::{DAffine2, DVec2, Vec2};
use graphene_resource::Resource;
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
use raster_types::Image;