Merge commit 'ciaran/grapher'

* commit 'ciaran/grapher':
  Grapher: add speed tests
  Discard branches that run for too long
  PBGitRevList: use --topo-order for better graphs
  PBGitGrapher: Improve the grapher
  First take on graphing
This commit is contained in:
Pieter de Bie
2008-08-27 18:10:59 +02:00
12 changed files with 455 additions and 42 deletions
+6
View File
@@ -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 = "<group>"; };
F5FF4E160E0829C20006317A /* PBGitRevList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitRevList.h; sourceTree = "<group>"; };
F5FF4E170E0829C20006317A /* PBGitRevList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitRevList.m; sourceTree = "<group>"; };
F5FF4E780E082E440006317A /* PBGitGrapher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitGrapher.h; sourceTree = "<group>"; };
F5FF4E790E082E440006317A /* PBGitGrapher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitGrapher.m; sourceTree = "<group>"; };
/* 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 */,
);
+5 -2
View File
@@ -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
+42
View File
@@ -0,0 +1,42 @@
//
// PBGitGrapher.h
// GitX
//
// Created by Pieter de Bie on 17-06-08.
// Copyright 2008 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#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
+143
View File
@@ -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
+3
View File
@@ -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
+9 -2
View File
@@ -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
+4 -2
View File
@@ -7,10 +7,12 @@
//
#import <Cocoa/Cocoa.h>
#import "PBGitGrapher.h"
@interface PBGitRevisionCell : NSTextFieldCell {
NSNumber* commit;
PBGitCellInfo cellInfo;
BOOL isReady;
}
@property(retain) NSNumber* commit;
@property(assign) PBGitCellInfo cellInfo;
@end
+81 -36
View File
@@ -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
+26
View File
@@ -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
+33
View File
@@ -0,0 +1,33 @@
#include <stdlib.h>
#include <stdio.h>
#include <xlocale.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <Cocoa/Cocoa.h>
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;
}
+55
View File
@@ -0,0 +1,55 @@
#include <stdlib.h>
#include <stdio.h>
#include <xlocale.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
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;
}
+48
View File
@@ -0,0 +1,48 @@
#include <stdlib.h>
#include <stdio.h>
#include <xlocale.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
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;
}