Skip to content

Commit

Permalink
Fixed image switching synchronization problems. Drank beer. It was good.
Browse files Browse the repository at this point in the history
git-svn-id: http://svn.hunch.se/photo-feeder/trunk@150 98834e0e-571d-0410-bf09-ac0e1acfc769
  • Loading branch information
rasmus committed Jan 24, 2007
1 parent b86665a commit 4433f42
Show file tree
Hide file tree
Showing 15 changed files with 5,025 additions and 363 deletions.
2 changes: 1 addition & 1 deletion Core/AIPlasticButton/AIPlasticButtonCell.m
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
fraction:1.0];

//Draw Label
#warning XXX handle NSCellImagePosition values other than these two correctly
//#warning XXX handle NSCellImagePosition values other than these two correctly
if(imagePosition != NSImageOnly) {
NSString *title = [self title];
if (title) {
Expand Down
5 changes: 4 additions & 1 deletion Core/PFMain.m
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ - (void) instantiateProviders
NSObject<PFProvider>* provider;

if(!identifier)
identifier = [PFUtil generateUniqueIdentifierForInstanceOfClass:providerClass];
identifier = [PFUtil generateUID];

if(!configuration)
configuration = [PFUtil configurationForProviderWithIdentifier:identifier];
Expand Down Expand Up @@ -451,6 +451,9 @@ - (void) queueFillerThread:(id)obj
// If we have an available provider, let's use it
if(provider)
{
// TEST
//[PFUtil randomSleep:0 maxSeconds:5];

// Put this procider in the "busy" stack
@synchronized(busyProviders)
{
Expand Down
5 changes: 2 additions & 3 deletions Core/PFUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@
/// Cause the current thread to sleep for random number of seconds
+ (void) randomSleep:(unsigned)min maxSeconds:(unsigned)max;

/// Generates a per-process unique identifier based on class
+ (NSString*) generateUniqueIdentifierForInstanceOfClass:(Class)cls;
/// Generates a globally unique identifier, valid between processes
+ (NSString*) generateUID;

+ (NSMutableDictionary*) configurationForProvider:(NSObject<PFProvider>*)provider;
+ (NSMutableDictionary*) configurationForProviderWithIdentifier:(NSString*)providerId;
+ (void) setConfiguration:(NSDictionary*)conf forProvider:(NSObject<PFProvider>*)provider;

/// Screeen saver defaults for PhotoFeeder
+ (NSUserDefaults*) defaults;
Expand Down
41 changes: 17 additions & 24 deletions Core/PFUtil.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@

#import "PFUtil.h"
#import <ScreenSaver/ScreenSaverDefaults.h>
#import <ScreenSaver/ScreenSaverView.h>

@implementation PFUtil


static NSMutableDictionary* uniqueIdentifiersDictKeyedByClass = nil;


+ (unsigned long) microseed
{
struct timeval tp;
Expand All @@ -40,10 +38,10 @@ + (double) microtime
+ (void) randomSleep:(unsigned)min maxSeconds:(unsigned)max
{
srandom([PFUtil microseed]);
unsigned long s = (random() % (max-min)) + min;
if(!s)
float s = SSRandomFloatBetween(min, max);
if(!s) // if 0, no need to sleep
return;
DLog(@"Sleeping for %lu seconds...", s);
//DLog(@"Sleeping for %f seconds...", s);
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:s]];
}

Expand All @@ -55,23 +53,18 @@ + (NSString*) providerIdFromProvider:(NSObject<PFProvider>*)provider
}


+ (NSString*) generateUniqueIdentifierForInstanceOfClass:(Class)cls
+ (NSString*) generateUID
{
if(!uniqueIdentifiersDictKeyedByClass)
uniqueIdentifiersDictKeyedByClass = [[NSMutableDictionary alloc] init];

NSString* className = NSStringFromClass(cls);
NSNumber* nextNumObj;
int nextNum = 0;
struct timeval tp;

@synchronized(uniqueIdentifiersDictKeyedByClass)
if(gettimeofday(&tp, NULL) == 0)
{
if(nextNumObj = [uniqueIdentifiersDictKeyedByClass objectForKey:className])
nextNum = [nextNumObj intValue];
[uniqueIdentifiersDictKeyedByClass setObject:[[NSNumber alloc] initWithInt:nextNum+1] forKey:className];
srandom(tp.tv_sec);
return [NSString stringWithFormat:@"%x-%x-%x",
tp.tv_usec, tp.tv_sec, SSRandomIntBetween(100, LONG_MAX)];
}

return [NSString stringWithFormat:@"%@#%d", className, nextNum];
return nil;
}


Expand Down Expand Up @@ -167,20 +160,20 @@ + (NSDictionary*) appDefaults
[NSDictionary dictionaryWithObjectsAndKeys:
@"PFDiskProvider", @"class",
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], @"active",
@"Images in ~/Pictures/_temp", @"name",
@"~/Pictures/_temp", @"path",
[NSNumber numberWithBool:YES], @"active",
@"Images in my Pictures folder", @"name",
@"~/Pictures", @"path",
nil],
@"configuration",
nil],
@"PFDiskProvider#-1",
@"787f7-45b684e7-4e413064",
nil];

// Application defaults
appDefaults = [[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:60], @"fps",
[NSNumber numberWithFloat:3.0], @"displayInterval",
[NSNumber numberWithFloat:1.0], @"fadeInterval",
[NSNumber numberWithFloat:5.0], @"displayInterval",
[NSNumber numberWithFloat:3.0], @"fadeInterval",
defaultActiveProviders, @"activeProviders",
nil] retain];
}
Expand Down
8 changes: 8 additions & 0 deletions Core/PFView.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
{
QCView* qcView; // The quartz composition
BOOL switchImageThreadsAreRunning;
BOOL hasResetTimer;
BOOL isFirstTime;

NSImage* sourceImage; // back
NSImage* destinationImage; // front
Expand All @@ -25,6 +27,12 @@
double userFadeInterval; // User-defined transition interval
double userDisplayInterval; // User-defined display interval -- how long the image is displayed, not counting transitions
double userFps;

// Update factors needed for image port switch synchronization
float imagePortMinSrc;
float imagePortMaxSrc;
float imagePortMinDst;
float imagePortMaxDst;
}

// Animation & Rendering
Expand Down
91 changes: 59 additions & 32 deletions Core/PFView.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
[qcView loadCompositionFromFile:[[[PFMain instance] bundle] pathForResource:@"standard" ofType:@"qtz"]];
[qcView setAutostartsRendering:NO];
[self addSubview: qcView];
[qcView setValue: [NSNumber numberWithDouble:1.0] forInputKey: @"statusMessageEnabled"];
}
return self;
}
Expand All @@ -60,12 +61,8 @@ - (void) startAnimation
DLog(@"");

[self renderingParametersDidChange];

[qcView setValue: @"Loading images..." forInputKey: @"statusMessageText"];

// Reset image ports
imagePortName = dstImageId;

// Fork threads on first call to startAnimation (pungsvett från räkan)
if(!switchImageThreadsAreRunning)
{
Expand All @@ -75,9 +72,11 @@ - (void) startAnimation
}

// Start animation timer and unlock "critical section"
hasResetTimer = NO;
isFirstTime = YES;
[super startAnimation];
[qcView startRendering];
[qcView setValue:[NSNumber numberWithBool: TRUE] forInputKey:@"resetTime"];
[qcView setValue:[NSNumber numberWithBool: TRUE] forInputKey:@"startTime"];
[[PFMain instance] animationStartedByView:self];
}

Expand All @@ -99,6 +98,12 @@ - (void) renderingParametersDidChange
[qcView setValue: [NSNumber numberWithDouble:userDisplayInterval] forInputKey: @"timeVisible"];
[qcView setValue: [NSNumber numberWithDouble:userFadeInterval] forInputKey: @"timeFading"];
[qcView setMaxRenderingFrameRate: userFps];

// Update factors needed for image port switch synchronization
imagePortMinSrc = (userFadeInterval*2)+userDisplayInterval;
imagePortMaxSrc = (userFadeInterval+userDisplayInterval)*2;
imagePortMinDst = userFadeInterval;
imagePortMaxDst = userFadeInterval+userDisplayInterval;
}


Expand Down Expand Up @@ -126,7 +131,7 @@ - (void) switchImageDispatchThread:(id)obj
switchImageThreadsAreRunning = YES;
@try
{
BOOL isFirstTime = YES;
isFirstTime = YES;
imagePortName = srcImageId;
double delay;

Expand All @@ -140,9 +145,7 @@ - (void) switchImageDispatchThread:(id)obj

// If we don't have an image yet, wait a short while before trying again.
if (delay == -1.0)
{
delay = 1.0;
}
else
isFirstTime = NO;

Expand All @@ -161,17 +164,47 @@ - (void) switchImageDispatchThread:(id)obj
#pragma mark Image switching


- (double) switchImage:(BOOL)isFirstTime

- (double) switchImage:(BOOL)isFirstTimee
{
NSImage* image;
double delay;
NSImage* image = nil;
double time, delay;
float imagePortMod;
int imagePortState;

image = [[[PFMain instance] queue] poll];
time = [[qcView valueForInputKey: @"time"] doubleValue];
imagePortMod = ceil(FMOD(time, (userDisplayInterval+userFadeInterval)*2));

if(imagePortMod >= imagePortMinSrc && imagePortMod <= imagePortMaxSrc)
{
imagePortName = srcImageId;
imagePortState = 1;
//DLog(@"------------------- SRC time: %.4f mod: %.4f src: %.1f - %.1f dst: %.1f - %.1f", time, imagePortMod,
// imagePortMinSrc, imagePortMaxSrc, imagePortMinDst, imagePortMaxDst);
}
else if(imagePortMod >= imagePortMinDst && imagePortMod <= imagePortMaxDst)
{
imagePortName = dstImageId;
imagePortState = 2;
//DLog(@"------------------- DST time: %.4f mod: %.4f src: %.1f - %.1f dst: %.1f - %.1f", time, imagePortMod,
// imagePortMinSrc, imagePortMaxSrc, imagePortMinDst, imagePortMaxDst);
}
else
{
// Error correction - if call is out of sync, this block is executed.
if(!isFirstTime)
imagePortName = (imagePortName == srcImageId) ? dstImageId : srcImageId;
imagePortState = 0;
//DLog(@"------------------- ? time: %.4f mod: %.4f src: %.1f - %.1f dst: %.1f - %.1f", time, imagePortMod,
// imagePortMinSrc, imagePortMaxSrc, imagePortMinDst, imagePortMaxDst);
}

// Take the next image from the image queue
image = (NSImage*)[[[PFMain instance] queue] poll];

// Check if queue is empty
if(!image)
{
//DLog(@"image = nil");
if(isFirstTime)
{
return -1.0;
Expand All @@ -183,35 +216,29 @@ - (double) switchImage:(BOOL)isFirstTime
DLog(@"Image queue is depleted");
}
}
else
else if(imagePortState && !isFirstTime)
{
[qcView setValue: [NSNumber numberWithDouble:0.0] forInputKey: @"statusMessageEnabled"];

// Pass the image to QC, which will cause the bitmap data to be copied onto a
// texture. Takes some time...
[qcView setValue:image forInputKey:imagePortName];
[image release];

// First time, we know the exact delay:
if(!hasResetTimer)
{
[qcView setValue:[NSNumber numberWithBool: TRUE] forInputKey:@"resetTime"];
delay = userFadeInterval;
hasResetTimer = YES;
}
}

// First time, we know the exact delay:
if(isFirstTime)
{
[qcView setValue:[NSNumber numberWithBool: TRUE] forInputKey:@"startTime"];
delay = userFadeInterval;
}
// Following calls, we sync the delay with the rendering cycle,
// getting "time" from the qc-composition:
else
{
double time = [[qcView valueForInputKey: @"time"] doubleValue];
double userDisplayAndFadeInterval = userDisplayInterval + userFadeInterval;
delay = (userDisplayAndFadeInterval - (time - (floor(time / userDisplayAndFadeInterval) * userDisplayAndFadeInterval))) + userFadeInterval;
}

// Switch image ports. Needs to be done every time this method is run.
imagePortName = (imagePortName == srcImageId) ? dstImageId : srcImageId;
//time = [[qcView valueForInputKey: @"time"] doubleValue]; // we don't need the exakthet säger räkans med pungsvett
double userDisplayAndFadeInterval = userDisplayInterval + userFadeInterval;
delay = (userDisplayAndFadeInterval - (time - (floor(time / userDisplayAndFadeInterval) * userDisplayAndFadeInterval))) + userFadeInterval;

DLog(@"Next switch will operate on '%@' in %f seconds", imagePortName, delay);
DLog(@"Next switch will operate in %f seconds", imagePortName, delay);
return delay;
}

Expand Down
3 changes: 3 additions & 0 deletions Core/prefix.pch
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
// S-formed curve from 0-1
#define SMOOTHSTEP(x) ((x)*(x)*(3-2*(x)))

// Modulus float
#define FMOD(x,y) ((x)-(floor((x)/(y))*(y)))

// Exception throwing shorthand:
// void throw_ex(NSString* type, NSString* message)
//
Expand Down
2 changes: 1 addition & 1 deletion PhotoFeeder.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@
3A848C620B3B720B00B65C13 /* Viewer_main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Viewer_main.m; sourceTree = "<group>"; };
3A848C630B3B720B00B65C13 /* ViewerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewerController.h; sourceTree = "<group>"; };
3A848C640B3B720B00B65C13 /* ViewerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewerController.m; sourceTree = "<group>"; };
3A8F91600AE571820027DC3B /* Viewer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Viewer.app; sourceTree = BUILT_PRODUCTS_DIR; };
3A8F91600AE571820027DC3B /* Viewer.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Viewer.app; sourceTree = BUILT_PRODUCTS_DIR; };
3A94047D0B59A133001FBD1C /* NSArrayPFAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSArrayPFAdditions.h; path = Core/NSArrayPFAdditions.h; sourceTree = "<group>"; };
3A94047E0B59A133001FBD1C /* NSArrayPFAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSArrayPFAdditions.m; path = Core/NSArrayPFAdditions.m; sourceTree = "<group>"; };
3A9404C40B59A8D7001FBD1C /* PFUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PFUtil.h; path = Core/PFUtil.h; sourceTree = "<group>"; };
Expand Down
Loading

0 comments on commit 4433f42

Please sign in to comment.