diff --git a/GitX.xcodeproj/project.pbxproj b/GitX.xcodeproj/project.pbxproj index 87a0b96..e85deac 100644 --- a/GitX.xcodeproj/project.pbxproj +++ b/GitX.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ F5B721C40E05CF7E00AF29DC /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F5B721C20E05CF7E00AF29DC /* MainMenu.xib */; }; F5DFFA6C0E075D8800617813 /* PBEasyFS.m in Sources */ = {isa = PBXBuildFile; fileRef = F5DFFA6B0E075D8800617813 /* PBEasyFS.m */; }; F5FF4E180E0829C20006317A /* PBGitRevList.m in Sources */ = {isa = PBXBuildFile; fileRef = F5FF4E170E0829C20006317A /* PBGitRevList.m */; }; + F5FF4E7A0E082E440006317A /* PBGitGrapher.m in Sources */ = {isa = PBXBuildFile; fileRef = F5FF4E790E082E440006317A /* PBGitGrapher.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -103,6 +104,8 @@ F5DFFA6B0E075D8800617813 /* PBEasyFS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBEasyFS.m; sourceTree = ""; }; F5FF4E160E0829C20006317A /* PBGitRevList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitRevList.h; sourceTree = ""; }; F5FF4E170E0829C20006317A /* PBGitRevList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitRevList.m; sourceTree = ""; }; + F5FF4E780E082E440006317A /* PBGitGrapher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitGrapher.h; sourceTree = ""; }; + F5FF4E790E082E440006317A /* PBGitGrapher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitGrapher.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -229,6 +232,8 @@ F56174540E05887E001DCD79 /* Git */ = { isa = PBXGroup; children = ( + F5FF4E780E082E440006317A /* PBGitGrapher.h */, + F5FF4E790E082E440006317A /* PBGitGrapher.m */, F5945E150E02B0C200706420 /* PBGitRepository.h */, F5945E160E02B0C200706420 /* PBGitRepository.m */, F56524EE0E02D45200F03B52 /* PBGitCommit.h */, @@ -385,6 +390,7 @@ F5DFFA6C0E075D8800617813 /* PBEasyFS.m in Sources */, F50FE0E30E07BE9600854FCD /* PBGitRevisionCell.m in Sources */, F5FF4E180E0829C20006317A /* PBGitRevList.m in Sources */, + F5FF4E7A0E082E440006317A /* PBGitGrapher.m in Sources */, 911111F80E594F3F00BF76B4 /* PBRepositoryDocumentController.m in Sources */, 913D5E5F0E556A9300CECEA2 /* PBCLIProxy.mm in Sources */, ); diff --git a/PBDetailController.m b/PBDetailController.m index dedfa33..d451a15 100644 --- a/PBDetailController.m +++ b/PBDetailController.m @@ -8,6 +8,7 @@ #import "PBDetailController.h" #import "CWQuickLook.h" +#import "PBGitGrapher.h" #define QLPreviewPanel NSClassFromString(@"QLPreviewPanel") @@ -148,7 +149,9 @@ if (![[aTableColumn identifier] isEqualToString:@"subject"]) return; - NSNumber* n = [NSNumber numberWithInt:(rowIndex % 2)]; - [aCell setCommit:n]; + if (self.repository.revisionList.grapher) { + PBGitGrapher* g = self.repository.revisionList.grapher; + [aCell setCellInfo: [g cellInfoForRow:rowIndex]]; + } } @end diff --git a/PBGitGrapher.h b/PBGitGrapher.h new file mode 100644 index 0000000..3a421f2 --- /dev/null +++ b/PBGitGrapher.h @@ -0,0 +1,42 @@ +// +// PBGitGrapher.h +// GitX +// +// Created by Pieter de Bie on 17-06-08. +// Copyright 2008 __MyCompanyName__. All rights reserved. +// + +#import +#import "PBGitCommit.h" + + +struct PBGitGraphColumn { + NSString* commit; // Commit that we're looking for + int color; +}; + + +#define PBGitMaxColumns 100 + +struct PBGitGraphCellInfo { + struct PBGitGraphColumn columns[PBGitMaxColumns]; + int upperMapping[PBGitMaxColumns]; //How are the offsets compared to previous cell? + int lowerMapping[PBGitMaxColumns]; //How are the offsets compared to this cell? + int position; + NSString* commit; // Commit in cell + int numColumns; + int numNewColumns; +}; + +void add_commit_to_graph(struct PBGitGraphCellInfo* info, NSString* parent, int* mapping_index); + +typedef struct PBGitGraphCellInfo PBGitCellInfo; + + +@interface PBGitGrapher : NSObject { + PBGitCellInfo* cellsInfo; +} + +- (void) parseCommits: (NSArray *) array; +- (struct PBGitGraphCellInfo) cellInfoForRow: (int) row; +@end diff --git a/PBGitGrapher.m b/PBGitGrapher.m new file mode 100644 index 0000000..e643807 --- /dev/null +++ b/PBGitGrapher.m @@ -0,0 +1,143 @@ +// +// PBGitGrapher.m +// GitX +// +// Created by Pieter de Bie on 17-06-08. +// Copyright 2008 __MyCompanyName__. All rights reserved. +// + +#import "PBGitGrapher.h" +#import "PBGitCommit.h" + +@implementation PBGitGrapher + + +- (void) parseCommits: (NSArray *) commits +{ + cellsInfo = malloc(sizeof(struct PBGitGraphCellInfo) * [commits count]); + memset(cellsInfo, 0, sizeof(struct PBGitGraphCellInfo) * [commits count]); + + int row = 0; + + struct PBGitGraphCellInfo *previous = nil; + for (PBGitCommit* commit in commits) { + struct PBGitGraphCellInfo *info = &(cellsInfo[row]); + info->commit = commit.sha; + info->numColumns = 0; + + int i = 0, newPos = -1; + for (i = 0; i < PBGitMaxColumns; i++) { + info->lowerMapping[i] = -1; + info->upperMapping[i] = -1; + } + + BOOL didFirst = NO; + // First, iterate over earlier columns and pass through any that don't want this commit + if (previous != nil) { + + // We can't count until numColumns here, as it's only used for the width of the cell. + for (i = 0; i < PBGitMaxColumns; i++) { + if ((previous->columns[i].commit) == nil) + continue; + + // This is our commit! We should do a "merge": move the line from + // our upperMapping to their lowerMapping + if ([previous->columns[i].commit isEqualToString:info->commit]) { + if (!didFirst) { + didFirst = YES; + info->position = info->numColumns++; + info->columns[info->position].commit = [commit.parents objectAtIndex:0]; + } + newPos = info->position; + info->upperMapping[i] = newPos; + } + else { + // We are not this commit. + // Try to find an earlier column for this commit. + int j; + BOOL found = NO; + for (j = 0; j < info->numColumns; j++) { + if (j == info->position) + continue; + if ([previous->columns[i].commit isEqualToString: info->columns[j].commit]) { + // We already have a column for this commit. use it instead + newPos = j; + info->upperMapping[previous->lowerMapping[i]] = newPos; + found = YES; + break; + } + } + // We need a new column for this. + if (!found) { + + if (previous->columns[i].color == 10) + continue; + + newPos = info->numColumns++; + info->columns[newPos] = previous->columns[i]; + info->columns[newPos].color++; + info->upperMapping[newPos] = newPos; + } + } + // For existing columns, we always just continue straight down + info->lowerMapping[newPos] = newPos; + } + } + + //Add your own parents + + // If we already did the first parent, don't do so again + if (!didFirst) { + info->position = info->numColumns++; + info->columns[info->position].commit = [commit.parents objectAtIndex:0]; + info->lowerMapping[info->position] = info->position; + } + + // Add all other parents + + // If we add at least one parent, we can go back a single column. + // This boolean will tell us if that happened + BOOL addedParent = NO; + + for (NSString* parent in [commit.parents subarrayWithRange:NSMakeRange(1, [commit.parents count] -1)]) { + int i; + BOOL was_displayed = NO; + for (i = 0; i < info->numColumns; i++) + if ([info->columns[i].commit isEqualToString: parent]) { + // TODO! + // !!! BUG + // This overwrites an existing mapping. + // We should instead have the possibility + // to add multiple lower mappings + // As we don't have that now, pieces of the graph are missing + info->lowerMapping[i] = info->position; + was_displayed = YES; + break; + } + if (was_displayed) + continue; + + // Really add this parent + addedParent = YES; + info->columns[info->numColumns++].commit = parent; + info->lowerMapping[info->numColumns -1] = info->position; + } + + // A parent was added, so we have room to not indent. + if (addedParent) + info->numColumns--; + previous = info; + ++row; + } +} +- (struct PBGitGraphCellInfo) cellInfoForRow: (int) row +{ + return cellsInfo[row]; +} + +- (void) finalize +{ + free(cellsInfo); + [super finalize]; +} +@end diff --git a/PBGitRevList.h b/PBGitRevList.h index 01ab75c..1417cac 100644 --- a/PBGitRevList.h +++ b/PBGitRevList.h @@ -12,6 +12,7 @@ @interface PBGitRevList : NSObject { NSArray* commits; NSArray* parameters; + id grapher; id repository; NSString* currentRef; } @@ -20,4 +21,6 @@ - readCommits; @property(retain) NSArray* commits; +@property(retain) id grapher; + @end diff --git a/PBGitRevList.m b/PBGitRevList.m index 56b427f..a627326 100644 --- a/PBGitRevList.m +++ b/PBGitRevList.m @@ -9,10 +9,11 @@ #import "PBGitRevList.h" #import "PBGitRepository.h" #import "PBGitCommit.h" +#import "PBGitGrapher.h" @implementation PBGitRevList -@synthesize commits; +@synthesize commits, grapher; - initWithRepository: (id) repo andRevListParameters: (NSArray*) params { parameters = params; @@ -101,10 +102,16 @@ } [self performSelectorOnMainThread:@selector(setCommits:) withObject:newArray waitUntilDone:YES]; + + PBGitGrapher* g = [[PBGitGrapher alloc] init]; + [g parseCommits: self.commits]; + [self performSelectorOnMainThread:@selector(setGrapher:) withObject:g waitUntilDone:YES]; + [self performSelectorOnMainThread:@selector(setCommits:) withObject:newArray waitUntilDone:YES]; + NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:start]; NSLog(@"Loaded %i commits in %f seconds", num, duration); - [NSThread exit]; } + @end diff --git a/PBGitRevisionCell.h b/PBGitRevisionCell.h index d0d047a..8523b9c 100644 --- a/PBGitRevisionCell.h +++ b/PBGitRevisionCell.h @@ -7,10 +7,12 @@ // #import +#import "PBGitGrapher.h" @interface PBGitRevisionCell : NSTextFieldCell { - NSNumber* commit; + PBGitCellInfo cellInfo; + BOOL isReady; } -@property(retain) NSNumber* commit; +@property(assign) PBGitCellInfo cellInfo; @end diff --git a/PBGitRevisionCell.m b/PBGitRevisionCell.m index 6028439..3d960e2 100644 --- a/PBGitRevisionCell.m +++ b/PBGitRevisionCell.m @@ -10,15 +10,79 @@ @implementation PBGitRevisionCell -@synthesize commit; + +@synthesize cellInfo; +-(void) setCellInfo: (PBGitCellInfo) info +{ + isReady = YES; + cellInfo = info; +} + +- (id) initWithCoder: (id) coder +{ + self = [super initWithCoder:coder]; + if (self != nil) { + isReady = NO; + } + return self; +} + +- (NSArray*) colors +{ + return [NSArray arrayWithObjects:[NSColor redColor], [NSColor blueColor], + [NSColor orangeColor], [NSColor blackColor], [NSColor greenColor], nil]; +} + +- (void) drawLineFromColumn: (int) from toColumn: (int) to inRect: (NSRect) r offset: (int) offset +{ + + int columnWidth = 10; + NSPoint origin = r.origin; + + NSPoint source = NSMakePoint(origin.x + columnWidth* from, origin.y + offset); + NSPoint center = NSMakePoint( origin.x + columnWidth * to, origin.y + r.size.height * 0.5); + + // Just use red for now. + [[[self colors] objectAtIndex:0] set]; + + NSBezierPath * path = [NSBezierPath bezierPath]; + [path setLineWidth:2]; + + [path moveToPoint: source]; + [path lineToPoint: center]; + [path stroke]; + +} + +- (void) drawCircleForColumn: (int) c inRect: (NSRect) r +{ + NSArray* col = [NSArray arrayWithObjects:[NSColor redColor], [NSColor blueColor], + [NSColor orangeColor], [NSColor blackColor], [NSColor greenColor], nil]; + + int columnWidth = 10; + NSPoint origin = r.origin; + NSPoint columnOrigin = { origin.x + columnWidth * c, origin.y}; + + NSRect oval = { columnOrigin.x - 5, columnOrigin.y + r.size.height * 0.5 - 5, 10, 10}; + + + NSBezierPath * path = [NSBezierPath bezierPath]; + path = [NSBezierPath bezierPathWithOvalInRect:oval]; + [[col objectAtIndex:cellInfo.columns[c].color] set]; + [path fill]; + + NSRect smallOval = { columnOrigin.x - 3, columnOrigin.y + r.size.height * 0.5 - 3, 6, 6}; + [[NSColor whiteColor] set]; + path = [NSBezierPath bezierPathWithOvalInRect:smallOval]; + [path fill]; +} - (void) drawWithFrame: (NSRect) rect inView:(NSView *)view { - - // Don't do all this drawing for now. - [super drawWithFrame:rect inView:view]; - return; - float pathWidth = 20; + if (!isReady) + return [super drawWithFrame:rect inView:view]; + + float pathWidth = 10 + 10 * cellInfo.numColumns; NSRect ownRect; NSDivideRect(rect, &ownRect, &rect, pathWidth, NSMinXEdge); @@ -26,42 +90,23 @@ // Adjust by removing the border ownRect.size.height += 2; ownRect.origin.y -= 1; + ownRect.origin.x += 10; - NSPoint origin = ownRect.origin; - NSPoint middle = { origin.x + pathWidth / 2, origin.y + ownRect.size.height * 0.5 }; + int column = 0; - [[NSColor redColor] set]; - NSBezierPath * path = [NSBezierPath bezierPath]; - [path moveToPoint:NSMakePoint(middle.x, origin.y)]; - [path setLineWidth:2]; - [path lineToPoint: NSMakePoint(middle.x, origin.y + ownRect.size.height)]; - [path stroke]; - [path setLineWidth:1]; - + // We can't iterate over numColumns here, as there may be connections to be drawn outside our columns. + for (column = 0; column < PBGitMaxColumns; column++) { + if (cellInfo.upperMapping[column] !=-1) + [self drawLineFromColumn:column toColumn: cellInfo.upperMapping[column] inRect:ownRect offset: 0]; + if (cellInfo.lowerMapping[column] !=-1) + [self drawLineFromColumn: column toColumn: cellInfo.lowerMapping[column] inRect:ownRect offset: ownRect.size.height]; + } - NSRect oval = { middle.x - 5, middle.y -5, 10, 10}; - [[NSColor orangeColor] set]; - path = [NSBezierPath bezierPathWithOvalInRect:oval]; - [path fill]; + [self drawCircleForColumn: cellInfo.position inRect: ownRect]; - if ([self.commit intValue] == 0) - [[NSColor redColor] set]; - else - [[NSColor blueColor] set]; - - [path stroke]; - - NSRect smallOval = { middle.x - 3, middle.y - 3, 6, 6}; - [[NSColor whiteColor] set]; - path = [NSBezierPath bezierPathWithOvalInRect:smallOval]; - [path fill]; - [[NSColor blackColor] set]; - [path stroke]; - [super drawWithFrame:rect inView:view]; - [[NSColor blueColor] set]; - //[path stroke]; + isReady = NO; } @end diff --git a/speed_test/README b/speed_test/README new file mode 100644 index 0000000..205aef6 --- /dev/null +++ b/speed_test/README @@ -0,0 +1,26 @@ +These tests demonstrate 3 different ways to allocate memory for the graph +viewer. + +The methods: + +1. global + This method allocates a global memory pool that is used by all structs. + It is the fastest method and should be easy to clean up. You do have to + make sure that any pointers to the memory used by others is cleaned up. +1. malloc + This methods does two mallocs for every iteration. It is slightly slower + (2x as slow), but won't require as much unfragmented memory. It is harder + to clean up this memory, as it requires an equal amount of free's. +2. array + This method uses NSMutableArray's to store the necessary information. It is + by far the slowest (10x slower than global) but will make use of + Objective-C's garbage collection. This is the easiest way to go if it isn't + too slow. Looping and creating the arrays takes about 2 seconds for 800k + iterations. The question is if this significantly slows down the work. + +Results: + + global: 0.18 seconds + malloc: 0.39 seconds + array: 1.90 seconds + diff --git a/speed_test/array.m b/speed_test/array.m new file mode 100644 index 0000000..58942dc --- /dev/null +++ b/speed_test/array.m @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include +#include + +int main() { + srandomdev(); + + int i = 0; struct list* last; + int num = atoi("8000000"); + + int size = 1000; + int totColumns = 10000; + int currentColumn = 0; + + NSMutableArray* array = [NSMutableArray arrayWithCapacity: 100*size]; + + for (i = 0; i < num; i++) { + int numColumns = i % 5; + + NSMutableArray* arr = [NSMutableArray arrayWithCapacity: numColumns]; + int j; + for (j = 0; j < numColumns; j++) + [arr addObject: @"Ha"]; + [array addObject: arr]; + } + + [array release]; + return 0; +} \ No newline at end of file diff --git a/speed_test/global.c b/speed_test/global.c new file mode 100644 index 0000000..f63068a --- /dev/null +++ b/speed_test/global.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include +struct list { + void* columns; + int numColumns; +}; +struct hash { + char value[40]; +}; + +int main() { + srandomdev(); + + int i = 0; struct list* last; + int num = atoi("8000000"); + + int size = 1000; + int totColumns = 10000; + int currentColumn = 0; + + /* Initialize initial list of revisions */ + struct list* revisionList = malloc(size * sizeof(struct list)); + struct hash* columns = malloc(totColumns * sizeof(struct hash)); + + struct hash standardColumn; + strcpy(standardColumn.value, "Haha pieter"); + for (i = 0; i < num; i++) { + if (size <= i) { + size *= 2; + revisionList = realloc(revisionList, size * sizeof(struct list)); + } + + struct list* a = revisionList + i; + a->numColumns = i % 5; + if (currentColumn + a->numColumns > totColumns) { + totColumns *= 2; + printf("Reallocing columns. New total: %i\n", totColumns); + columns = realloc(columns, totColumns * sizeof(struct hash)); + } + int j; + for (j = 0; j < a->numColumns; j++) { + //ccolumns[currentColumn++] = st + strncpy(columns[currentColumn++].value, "Haha pieter is cool", 20); + } + } + + printf("Num value at 3000 is: %i vs %i\n", revisionList[3000].numColumns, (int) (5 * random())); + printf("Value of 1000'd column is: %s\n", columns[1000].value); + sleep(5); + return 0; +} \ No newline at end of file diff --git a/speed_test/malloc.c b/speed_test/malloc.c new file mode 100644 index 0000000..5fbadca --- /dev/null +++ b/speed_test/malloc.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include + +struct list { + struct hash* columns; + int numColumns; +}; +struct hash { + char value[40]; +}; + +int main() { + srandomdev(); + + int i = 0; struct list* last; + int num = atoi("8000000"); + + int size = 1000; + /* Initialize initial list of revisions */ + struct list** revisionList = malloc(size * sizeof(struct list*)); + + struct hash standardColumn; + strcpy(standardColumn.value, "Haha pieter"); + for (i = 0; i < num; i++) { + if (size <= i) { + size *= 2; + revisionList = realloc(revisionList, size * sizeof(struct list*)); + } + + struct list* a = malloc(sizeof(struct list)); + revisionList[i] = a; + + a->numColumns = i % 5; + a->columns = malloc(a->numColumns * sizeof(struct hash)); + int j; + for (j = 0; j < a->numColumns; j++) { + //ccolumns[currentColumn++] = st + strncpy(a->columns[j].value, "Haha pieter is cool", 20); + } + } + + printf("Num value at 3000 is: %i vs %i\n", revisionList[3000]->numColumns, (int) (5 * random())); + return 0; +} \ No newline at end of file