Context: These are additional features developed during the ComfyUI v1.34+ compatibility fix that weren't included in the minimal PR to keep the fix focused. They may be valuable for future improvements.
Original commit: 735c997 (with debug logging and content-based comparison)
Minimal PR commit: 92e7173 (clean fix without these features)
ComfyUI's execution caching may pass the same tensor reference even when content has changed. The current object identity check (is not) can miss these cases.
Add IS_CHANGED classmethod that computes a fingerprint based on tensor content:
# For PreviewBridge
@classmethod
def IS_CHANGED(cls, images, image, unique_id, block=False, restore_mask="never", **kwargs):
"""Compute cache key based on image content fingerprint.
This ensures ComfyUI properly invalidates the node cache when the input
image tensor content changes, even if the object reference is the same.
"""
import hashlib
# Handle case where images is None (can happen during graph validation)
if images is None:
return "no_images"
# Quick fingerprint: shape + sample pixels from corners
shape_str = f"{images.shape}"
try:
if images.numel() > 0:
flat = images.reshape(-1)
# Sample first and last 4 values as fingerprint
first = flat[:min(4, flat.numel())].cpu().numpy().tobytes()
last = flat[-min(4, flat.numel()):].cpu().numpy().tobytes()
else:
first = last = b''
except Exception:
first = last = b''
fingerprint = hashlib.md5(shape_str.encode() + first + last).hexdigest()
# Include restore_mask in key so mode changes trigger refresh
return f"{fingerprint}_{restore_mask}"# For PreviewBridgeLatent
@classmethod
def IS_CHANGED(cls, latent, image, preview_method, vae_opt=None, block=False, unique_id=None, restore_mask='never', **kwargs):
"""Compute cache key based on latent content fingerprint."""
import hashlib
samples = latent.get('samples')
if samples is None:
return "no_samples"
shape_str = f"{samples.shape}"
try:
if samples.numel() > 0:
flat = samples.reshape(-1)
first = flat[:min(4, flat.numel())].cpu().numpy().tobytes()
last = flat[-min(4, flat.numel()):].cpu().numpy().tobytes()
else:
first = last = b''
except Exception:
first = last = b''
fingerprint = hashlib.md5(shape_str.encode() + first + last).hexdigest()
# Include restore_mask and preview_method in key so changes trigger refresh
return f"{fingerprint}_{restore_mask}_{preview_method}"- Detects content changes even when object reference is the same
- Includes
restore_maskmode in cache key so mode changes trigger refresh - Low overhead (only samples 8 values total)
The current cache comparison uses is not (object identity), which can fail when ComfyUI's execution caching passes the same tensor reference with different content.
Replace object identity with content-based comparison:
# For PreviewBridge
@staticmethod
def _images_match(cached, current):
"""Compare images using shape and sample pixels for content equality.
This replaces object identity check (is not) with content-based comparison.
"""
if cached is None or current is None:
return cached is current
if cached.shape != current.shape:
return False
# Quick sample comparison - check corners
try:
flat_cached = cached.reshape(-1)
flat_current = current.reshape(-1)
n = min(4, flat_cached.numel())
# Compare first and last samples
first_match = torch.equal(flat_cached[:n], flat_current[:n])
last_match = torch.equal(flat_cached[-n:], flat_current[-n:])
return first_match and last_match
except Exception:
return False# For PreviewBridgeLatent
@staticmethod
def _latents_match(cached_latent, current_latent):
"""Compare latents using shape and sample values for content equality."""
if cached_latent is None or current_latent is None:
return cached_latent is current_latent
cached_samples = cached_latent.get('samples')
current_samples = current_latent.get('samples')
if cached_samples is None or current_samples is None:
return cached_samples is current_samples
if cached_samples.shape != current_samples.shape:
return False
# Quick sample comparison - check corners
try:
flat_cached = cached_samples.reshape(-1)
flat_current = current_samples.reshape(-1)
n = min(4, flat_cached.numel())
first_match = torch.equal(flat_cached[:n], flat_current[:n])
last_match = torch.equal(flat_cached[-n:], flat_current[-n:])
return first_match and last_match
except Exception:
return FalseReplace the object identity check:
# Before (object identity)
elif core.preview_bridge_cache[unique_id][0] is not images:
# After (content-based)
elif not PreviewBridge._images_match(core.preview_bridge_cache[unique_id][0], images):Enable verbose logging for diagnosing issues without modifying code.
console.log('[PreviewBridge] impact-image-util.js LOADED - NEW APPROACH v3');
// In nodeCreated:
console.log('[PreviewBridge] nodeCreated called for:', node.comfyClass, 'node.id:', node.id);
// In onExecuted:
console.log('[PreviewBridge] onExecuted: resetting widget value to:', newValue);
console.log('[PreviewBridge] onExecuted: current widget value was:', w.value);
console.log('[PreviewBridge] onExecuted: widget value after reset:', w.value);
// In imgs setter:
console.log('[PreviewBridge] imgs SETTER called, v length:', v ? v.length : 'null');
console.log('[PreviewBridge] imgs SETTER current widget value:', w.value);# In IS_CHANGED:
logging.warning(f"[PreviewBridge DEBUG] IS_CHANGED called for unique_id={unique_id}")
logging.warning(f"[PreviewBridge DEBUG] IS_CHANGED returning: {result[:20]}...")
# In doit():
logging.warning(f"[PreviewBridge DEBUG] doit called: unique_id={unique_id}, restore_mask={restore_mask}")
logging.warning(f"[PreviewBridge DEBUG] images shape: {images.shape}, image widget: {image[:50] if image else 'None'}...")
logging.warning(f"[PreviewBridge DEBUG] cache exists: {unique_id in core.preview_bridge_cache}")
logging.warning(f"[PreviewBridge DEBUG] Cache miss - need_refresh=True, images_changed=True")
logging.warning(f"[PreviewBridge DEBUG] Images DON'T match - need_refresh=True, images_changed=True")
logging.warning(f"[PreviewBridge DEBUG] Images MATCH - need_refresh=False, images_changed=False")
logging.warning(f"[PreviewBridge DEBUG] Saved NEW image: {image2[0]['filename']}")import os
PB_DEBUG = os.environ.get('IMPACT_PREVIEW_BRIDGE_DEBUG', '').lower() == 'true'
def pb_log(msg):
if PB_DEBUG:
logging.warning(f"[PB DEBUG] {msg}")The minimal PR focused solely on fixing the ComfyUI v1.34+ compatibility issue. These enhancements address potential edge cases that weren't observed during testing:
- IS_CHANGED - May help with ComfyUI's execution caching, but the current fix works without it
- Content comparison - Object identity currently works in tested scenarios
- Debug logging - Useful during development but adds noise to production logs
These could be valuable additions if users report edge cases where the minimal fix doesn't fully address their workflow.
- PR #1172: ltdrdata/ComfyUI-Impact-Pack#1172
- Minimal fix commit:
92e7173 - Full feature commit:
735c997