the whole game

This commit is contained in:
Kolyah35
2026-03-02 22:04:18 +03:00
parent 816e9060b4
commit f0617a5d22
2069 changed files with 581500 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
//
// IASKAppSettingsViewController.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import "IASKSettingsStore.h"
@class IASKSettingsReader;
@class IASKAppSettingsViewController;
@class IASKSpecifier;
@class IASKSwitch;
@class IASKPSToggleSwitchSpecifierViewCell;
@protocol IASKSettingsDelegate
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController*)sender;
@optional
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderForKey:(NSString*)key;
- (UIView *)tableView:(UITableView *)tableView viewForHeaderForKey:(NSString*)key;
- (CGFloat)tableView:(UITableView*)tableView heightForSpecifier:(IASKSpecifier*)specifier;
- (UITableViewCell*)tableView:(UITableView*)tableView cellForSpecifier:(IASKSpecifier*)specifier;
- (NSString*)mailComposeBody;
//- (UIViewController<MFMailComposeViewControllerDelegate>*)viewControllerForMailComposeView; //@iaskmail
//- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error;
- (void)settingsViewController:(IASKAppSettingsViewController*)sender buttonTappedForKey:(NSString*)key;
@end
@interface IASKAppSettingsViewController : UITableViewController <UITextFieldDelegate, UINavigationControllerDelegate/*, MFMailComposeViewControllerDelegate*/> {
id<IASKSettingsDelegate> _delegate;
NSMutableArray *_viewList;
NSIndexPath *_currentIndexPath;
IASKSettingsReader *_settingsReader;
id<IASKSettingsStore> _settingsStore;
NSString *_file;
id _currentFirstResponder;
BOOL _showCreditsFooter;
BOOL _showDoneButton;
NSMutableDictionary* _cells;
}
@property (nonatomic, assign) IBOutlet id delegate;
@property (nonatomic, retain) IASKSettingsReader *settingsReader;
@property (nonatomic, retain) id<IASKSettingsStore> settingsStore;
@property (nonatomic, copy) NSString *file;
@property (nonatomic, assign) BOOL showCreditsFooter;
@property (nonatomic, assign) BOOL showDoneButton;
- (void)synchronizeSettings;
- (IBAction)dismiss:(id)sender;
- (IASKSwitch*) getSwitch:(NSString*) key;
- (IASKPSToggleSwitchSpecifierViewCell*) getSwitchView:(NSString*) key;
- (void)setEnabled:(BOOL)status forKey:(NSString*)key;
- (void)toggledValue:(id)sender;
@end

View File

@@ -0,0 +1,850 @@
//
// IASKAppSettingsViewController.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2009-2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKAppSettingsViewController.h"
#import "IASKSettingsReader.h"
#import "IASKSettingsStoreUserDefaults.h"
#import "IASKPSToggleSwitchSpecifierViewCell.h"
#import "IASKPSSliderSpecifierViewCell.h"
#import "IASKPSTextFieldSpecifierViewCell.h"
#import "IASKPSTitleValueSpecifierViewCell.h"
#import "IASKSwitch.h"
#import "IASKSlider.h"
#import "IASKSpecifier.h"
#import "IASKSpecifierValuesViewController.h"
#import "IASKTextField.h"
static const CGFloat KEYBOARD_ANIMATION_DURATION = 0.3;
static const CGFloat MINIMUM_SCROLL_FRACTION = 0.2;
static const CGFloat MAXIMUM_SCROLL_FRACTION = 0.8;
static NSString *kIASKCredits = @"Powered by InAppSettingsKit"; // Leave this as-is!!!
#define kIASKSpecifierValuesViewControllerIndex 0
#define kIASKSpecifierChildViewControllerIndex 1
#define kIASKCreditsViewWidth 285
CGRect IASKCGRectSwap(CGRect rect);
@interface IASKAppSettingsViewController ()
@property (nonatomic, retain) NSMutableArray *viewList;
@property (nonatomic, retain) NSIndexPath *currentIndexPath;
@property (nonatomic, retain) id currentFirstResponder;
- (void)_textChanged:(id)sender;
- (void)synchronizeSettings;
- (void)reload;
@end
@implementation IASKAppSettingsViewController
@synthesize delegate = _delegate;
@synthesize viewList = _viewList;
@synthesize currentIndexPath = _currentIndexPath;
@synthesize settingsReader = _settingsReader;
@synthesize file = _file;
@synthesize currentFirstResponder = _currentFirstResponder;
@synthesize showCreditsFooter = _showCreditsFooter;
@synthesize showDoneButton = _showDoneButton;
@synthesize settingsStore = _settingsStore;
#pragma mark accessors
- (IASKSettingsReader*)settingsReader {
if (!_settingsReader) {
_settingsReader = [[IASKSettingsReader alloc] initWithFile:self.file];
}
return _settingsReader;
}
- (id<IASKSettingsStore>)settingsStore {
if (!_settingsStore) {
_settingsStore = [[IASKSettingsStoreUserDefaults alloc] init];
}
return _settingsStore;
}
- (NSString*)file {
if (!_file) {
return @"Root";
}
return [[_file retain] autorelease];
}
- (void)setFile:(NSString *)file {
if (file != _file) {
[_file release];
_file = [file copy];
}
self.tableView.contentOffset = CGPointMake(0, 0);
self.settingsReader = nil; // automatically initializes itself
}
- (BOOL)isPad {
BOOL isPad = NO;
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 30200)
isPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
#endif
return isPad;
}
#pragma mark standard view controller methods
- (id)init {
return [self initWithNibName:@"IASKAppSettingsView" bundle:nil];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
// If set to YES, will display credits for InAppSettingsKit creators
_showCreditsFooter = YES;
// If set to YES, will add a DONE button at the right of the navigation bar
_showDoneButton = YES;
_cells = [[NSMutableDictionary alloc] init];
if ([self isPad]) {
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLineEtched;
}
}
return self;
}
- (void)awakeFromNib {
// If set to YES, will display credits for InAppSettingsKit creators
_showCreditsFooter = YES;
// If set to YES, will add a DONE button at the right of the navigation bar
// if loaded via NIB, it's likely we sit in a TabBar- or NavigationController
// and thus don't need the Done button
_showDoneButton = NO;
if ([self isPad]) {
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLineEtched;
}
}
- (NSMutableArray *)viewList {
if (!_viewList) {
_viewList = [[NSMutableArray alloc] init];
[_viewList addObject:[NSDictionary dictionaryWithObjectsAndKeys:@"IASKSpecifierValuesView", @"ViewName",nil]];
[_viewList addObject:[NSDictionary dictionaryWithObjectsAndKeys:@"IASKAppSettingsView", @"ViewName",nil]];
}
return _viewList;
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.view = nil;
self.viewList = nil;
}
- (void)viewWillAppear:(BOOL)animated {
[self.tableView reloadData];
self.navigationItem.rightBarButtonItem = nil;
self.navigationController.delegate = self;
if (_showDoneButton) {
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(dismiss:)];
self.navigationItem.rightBarButtonItem = buttonItem;
[buttonItem release];
}
if (!self.title) {
self.title = NSLocalizedString(@"Settings", @"");
}
if (self.currentIndexPath) {
if (animated) {
// animate deselection of previously selected row
[self.tableView selectRowAtIndexPath:self.currentIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[self.tableView deselectRowAtIndexPath:self.currentIndexPath animated:YES];
}
self.currentIndexPath = nil;
}
[super viewWillAppear:animated];
}
- (CGSize)contentSizeForViewInPopover {
return [[self view] sizeThatFits:CGSizeMake(320, 2000)];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
IASK_IF_IOS4_OR_GREATER([dc addObserver:self selector:@selector(synchronizeSettings) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];);
IASK_IF_IOS4_OR_GREATER([dc addObserver:self selector:@selector(reload) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]];);
[dc addObserver:self selector:@selector(synchronizeSettings) name:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication]];
//NSLog(@"I have loaded shitzlets now!\n");
for( NSString *key in _cells )
{
id value = [_cells objectForKey:key];
if ([value respondsToSelector:@selector(toggle)]) {
//NSLog(@"Responds to Toggle: %@!\n", key);
[self toggledValue:[value toggle]];
//[value toggle].on = [value toggle].on;
}
// do something
}
}
- (void)viewWillDisappear:(BOOL)animated {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (!self.navigationController.delegate) {
// hide the keyboard when we're popping from the navigation controller
[self.currentFirstResponder resignFirstResponder];
}
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (![viewController isKindOfClass:[IASKAppSettingsViewController class]] && ![viewController isKindOfClass:[IASKSpecifierValuesViewController class]]) {
[self dismiss:nil];
}
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_viewList release], _viewList = nil;
[_currentIndexPath release], _currentIndexPath = nil;
[_file release], _file = nil;
[_currentFirstResponder release], _currentFirstResponder = nil;
[_settingsReader release], _settingsReader = nil;
[_settingsStore release], _settingsStore = nil;
[_cells release]; _cells = nil;
_delegate = nil;
[super dealloc];
}
#pragma mark -
#pragma mark Actions
- (IBAction)dismiss:(id)sender {
[self.settingsStore synchronize];
self.navigationController.delegate = nil;
if (self.delegate && [self.delegate conformsToProtocol:@protocol(IASKSettingsDelegate)]) {
[self.delegate settingsViewControllerDidEnd:self];
}
}
- (void)toggledValue:(id)sender {
IASKSwitch *toggle = (IASKSwitch*)sender;
IASKSpecifier *spec = [_settingsReader specifierForKey:[toggle key]];
if ([toggle isOn]) {
if ([spec trueValue] != nil) {
[self.settingsStore setObject:[spec trueValue] forKey:[toggle key]];
}
else {
[self.settingsStore setBool:YES forKey:[toggle key]];
}
}
else {
if ([spec falseValue] != nil) {
[self.settingsStore setObject:[spec falseValue] forKey:[toggle key]];
}
else {
[self.settingsStore setBool:NO forKey:[toggle key]];
}
}
//NSLog(@"Toggled: %@\n", [toggle key]);
[[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged
object:[toggle key]
userInfo:[NSDictionary dictionaryWithObject:[self.settingsStore objectForKey:[toggle key]]
forKey:[toggle key]]];
}
- (void)sliderChangedValue:(id)sender {
IASKSlider *slider = (IASKSlider*)sender;
[self.settingsStore setFloat:[slider value] forKey:[slider key]];
[[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged
object:[slider key]
userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:[slider value]]
forKey:[slider key]]];
}
#pragma mark -
#pragma mark UITableView Functions
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.settingsReader numberOfSections];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.settingsReader numberOfRowsForSection:section];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath];
if ([[specifier type] isEqualToString:kIASKCustomViewSpecifier]) {
if ([self.delegate respondsToSelector:@selector(tableView:heightForSpecifier:)]) {
return [self.delegate tableView:tableView heightForSpecifier:specifier];
} else {
return 0;
}
}
return tableView.rowHeight;
}
- (NSString *)tableView:(UITableView*)tableView titleForHeaderInSection:(NSInteger)section {
NSString *header = [self.settingsReader titleForSection:section];
if (0 == header.length) {
return nil;
}
return header;
}
- (UIView *)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section {
NSString *key = [self.settingsReader keyForSection:section];
if ([self.delegate respondsToSelector:@selector(tableView:viewForHeaderForKey:)]) {
return [self.delegate tableView:tableView viewForHeaderForKey:key];
} else {
return nil;
}
}
- (CGFloat)tableView:(UITableView*)tableView heightForHeaderInSection:(NSInteger)section {
NSString *key = [self.settingsReader keyForSection:section];
if ([self tableView:tableView viewForHeaderInSection:section] && [self.delegate respondsToSelector:@selector(tableView:heightForHeaderForKey:)]) {
CGFloat result;
if ((result = [self.delegate tableView:tableView heightForHeaderForKey:key])) {
return result;
}
}
NSString *title;
if ((title = [self tableView:tableView titleForHeaderInSection:section])) {
CGSize size = [title sizeWithFont:[UIFont boldSystemFontOfSize:[UIFont labelFontSize]]
constrainedToSize:CGSizeMake(tableView.frame.size.width - 2*kIASKHorizontalPaddingGroupTitles, INFINITY)
lineBreakMode:UILineBreakModeWordWrap];
return size.height+kIASKVerticalPaddingGroupTitles;
}
return 0;
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
{
NSString *footerText = [self.settingsReader footerTextForSection:section];
if (_showCreditsFooter && (section == [self.settingsReader numberOfSections]-1)) {
// show credits since this is the last section
if ((footerText == nil) || ([footerText length] == 0)) {
// show the credits on their own
return kIASKCredits;
} else {
// show the credits below the app's FooterText
return [NSString stringWithFormat:@"%@\n\n%@", footerText, kIASKCredits];
}
} else {
if ([footerText length] == 0) {
return nil;
}
return [self.settingsReader footerTextForSection:section];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//NSLog(@"CELL FOR ROW: %@\n", indexPath);
IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath];
NSString *key = [specifier key];
if ([[specifier type] isEqualToString:kIASKCustomViewSpecifier] && [self.delegate respondsToSelector:@selector(tableView:cellForSpecifier:)]) {
return [self.delegate tableView:tableView cellForSpecifier:specifier];
}
//NSString *reuseId = [specifier key];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if ([[specifier type] isEqualToString:kIASKPSToggleSwitchSpecifier]) {
if (!cell) {
cell = (IASKPSToggleSwitchSpecifierViewCell*) [[[NSBundle mainBundle] loadNibNamed:@"IASKPSToggleSwitchSpecifierViewCell"
owner:self
options:nil] objectAtIndex:0];
}
((IASKPSToggleSwitchSpecifierViewCell*)cell).label.text = [specifier title];
id currentValue = [self.settingsStore objectForKey:key];
BOOL toggleState;
if (currentValue) {
if ([currentValue isEqual:[specifier trueValue]]) {
toggleState = YES;
} else if ([currentValue isEqual:[specifier falseValue]]) {
toggleState = NO;
} else {
toggleState = [currentValue boolValue];
}
} else {
toggleState = [specifier defaultBoolValue];
}
((IASKPSToggleSwitchSpecifierViewCell*)cell).toggle.on = toggleState;
[((IASKPSToggleSwitchSpecifierViewCell*)cell).toggle addTarget:self action:@selector(toggledValue:) forControlEvents:UIControlEventValueChanged];
[((IASKPSToggleSwitchSpecifierViewCell*)cell).toggle setKey:key];
// [((IASKPSToggleSwitchSpecifierViewCell*)cell).toggle
[_cells setObject:cell forKey:key];
//NSLog(@"Setting cell: %@ : %p == %p?\n", key, cell, [_cells objectForKey:key]);
//[((IASKPSToggleSwitchSpecifierViewCell*)cell).toggle setEnabled:NO]; // @iaskattn
return cell;
}
else if ([[specifier type] isEqualToString:kIASKPSMultiValueSpecifier]) {
if (!cell) {
cell = [[[IASKPSTitleValueSpecifierViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:[specifier type]] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.backgroundColor = [UIColor whiteColor];
}
[[cell textLabel] setText:[specifier title]];
[[cell detailTextLabel] setText:[[specifier titleForCurrentValue:[self.settingsStore objectForKey:key] != nil ?
[self.settingsStore objectForKey:key] : [specifier defaultValue]] description]];
[_cells setValue:cell forKey:key];
return cell;
}
else if ([[specifier type] isEqualToString:kIASKPSTitleValueSpecifier]) {
if (!cell) {
cell = [[[IASKPSTitleValueSpecifierViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:[specifier type]] autorelease];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.backgroundColor = [UIColor whiteColor];
}
cell.textLabel.text = [specifier title];
id value = [self.settingsStore objectForKey:key] ? : [specifier defaultValue];
NSString *stringValue;
if ([specifier multipleValues] || [specifier multipleTitles]) {
stringValue = [specifier titleForCurrentValue:value];
} else {
stringValue = [value description];
}
cell.detailTextLabel.text = stringValue;
[cell setUserInteractionEnabled:NO];
[_cells setValue:cell forKey:key];
return cell;
}
else if ([[specifier type] isEqualToString:kIASKPSTextFieldSpecifier]) {
if (!cell) {
cell = (IASKPSTextFieldSpecifierViewCell*) [[[NSBundle mainBundle] loadNibNamed:@"IASKPSTextFieldSpecifierViewCell"
owner:self
options:nil] objectAtIndex:0];
((IASKPSTextFieldSpecifierViewCell*)cell).textField.textAlignment = UITextAlignmentLeft;
((IASKPSTextFieldSpecifierViewCell*)cell).textField.returnKeyType = UIReturnKeyDone;
((IASKPSTextFieldSpecifierViewCell*)cell)->stringIfEmpty = [specifier stringIfEmpty];
cell.accessoryType = UITableViewCellAccessoryNone;
}
((IASKPSTextFieldSpecifierViewCell*)cell).label.text = [specifier title];
NSString *textValue = [self.settingsStore objectForKey:key] != nil ? [self.settingsStore objectForKey:key] : [specifier defaultStringValue];
if (textValue && ![textValue isMemberOfClass:[NSString class]]) {
textValue = [NSString stringWithFormat:@"%@", textValue];
}
IASKTextField *textField = ((IASKPSTextFieldSpecifierViewCell*)cell).textField;
textField. text = textValue;
textField.key = key;
textField.delegate = self;
textField.secureTextEntry = [specifier isSecure];
textField.keyboardType = [specifier keyboardType];
textField.autocapitalizationType = [specifier autocapitalizationType];
[textField addTarget:self action:@selector(_textChanged:) forControlEvents:UIControlEventEditingChanged];
if([specifier isSecure]){
textField.autocorrectionType = UITextAutocorrectionTypeNo;
} else {
textField.autocorrectionType = [specifier autoCorrectionType];
}
[cell setNeedsLayout];
[_cells setValue:cell forKey:key];
return cell;
}
else if ([[specifier type] isEqualToString:kIASKPSSliderSpecifier]) {
if (!cell) {
cell = (IASKPSSliderSpecifierViewCell*) [[[NSBundle mainBundle] loadNibNamed:@"IASKPSSliderSpecifierViewCell"
owner:self
options:nil] objectAtIndex:0];
[(IASKPSSliderSpecifierViewCell*)cell initDefaults];
}
if ([specifier hasSpecified:kIASKTextOffsetPixels]) {
((IASKPSSliderSpecifierViewCell*)cell)->textOffsetPixels = [specifier textOffsetPixels];
}
if ([[specifier minimumValueImage] length] > 0) {
((IASKPSSliderSpecifierViewCell*)cell).minImage.image = [UIImage imageWithContentsOfFile:[_settingsReader pathForImageNamed:[specifier minimumValueImage]]];
}
if ([[specifier maximumValueImage] length] > 0) {
((IASKPSSliderSpecifierViewCell*)cell).maxImage.image = [UIImage imageWithContentsOfFile:[_settingsReader pathForImageNamed:[specifier minimumValueImage]]];
}
if ([[specifier title] length] > 0) {
((IASKPSSliderSpecifierViewCell*)cell).label.text = [specifier title];
}
IASKSlider *slider = ((IASKPSSliderSpecifierViewCell*)cell).slider;
slider.minimumValue = [specifier minimumValue];
slider.maximumValue = [specifier maximumValue];
slider.value = [self.settingsStore objectForKey:key] != nil ? [[self.settingsStore objectForKey:key] floatValue] : [[specifier defaultValue] floatValue];
[slider addTarget:self action:@selector(sliderChangedValue:) forControlEvents:UIControlEventValueChanged];
slider.key = key;
[cell setNeedsLayout];
[_cells setValue:cell forKey:key];
return cell;
}
else if ([[specifier type] isEqualToString:kIASKPSChildPaneSpecifier]) {
if (!cell) {
cell = [[[IASKPSTitleValueSpecifierViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:[specifier type]] autorelease];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
cell.backgroundColor = [UIColor whiteColor];
}
[[cell textLabel] setText:[specifier title]];
[_cells setValue:cell forKey:key];
return cell;
} else if ([[specifier type] isEqualToString:kIASKOpenURLSpecifier]) {
if (!cell) {
cell = [[[IASKPSTitleValueSpecifierViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:[specifier type]] autorelease];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
cell.backgroundColor = [UIColor whiteColor];
}
cell.textLabel.text = [specifier title];
cell.detailTextLabel.text = [[specifier defaultValue] description];
[_cells setValue:cell forKey:key];
return cell;
} else if ([[specifier type] isEqualToString:kIASKButtonSpecifier]) {
if (!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[specifier type]] autorelease];
cell.backgroundColor = [UIColor whiteColor];
}
cell.textLabel.text = [specifier title];
cell.textLabel.textAlignment = UITextAlignmentCenter;
[_cells setValue:cell forKey:key];
return cell;
} else if ([[specifier type] isEqualToString:kIASKMailComposeSpecifier]) {
if (!cell) {
cell = [[[IASKPSTitleValueSpecifierViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:[specifier type]] autorelease];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
cell.backgroundColor = [UIColor whiteColor];
}
cell.textLabel.text = [specifier title];
cell.detailTextLabel.text = [[specifier defaultValue] description];
[_cells setValue:cell forKey:key];
return cell;
} else {
if (!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[specifier type]] autorelease];
cell.backgroundColor = [UIColor whiteColor];
}
[[cell textLabel] setText:[specifier title]];
[_cells setValue:cell forKey:key];
return cell;
}
}
- (IASKSwitch*) getSwitch:(NSString*) key {
return [[_cells objectForKey:key] toggle];
}
- (IASKPSToggleSwitchSpecifierViewCell*) getSwitchView:(NSString*) key {
return [_cells objectForKey:key];
}
- (void)setEnabled:(BOOL)status forKey:(NSString*)key {
id value = [_cells objectForKey:key];
//NSLog(@"key: %@, value: %p\n (toggle: %p)\n", key, value, [value toggle]);
if (value == nil) return;
[[value label] setEnabled:status];
// This is lazy town! Try to handle it as a toggle, slider or textfield
if ([value respondsToSelector:@selector(toggle)]) [[value toggle] setEnabled:status];
if ([value respondsToSelector:@selector(slider)]) [[value slider] setEnabled:status];
if ([value respondsToSelector:@selector(textField)])[[value textField] setEnabled:status];
}
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath];
if ([[specifier type] isEqualToString:kIASKPSToggleSwitchSpecifier]) {
return nil;
} else {
return indexPath;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath];
if ([[specifier type] isEqualToString:kIASKPSToggleSwitchSpecifier]) {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
else if ([[specifier type] isEqualToString:kIASKPSMultiValueSpecifier]) {
IASKSpecifierValuesViewController *targetViewController = [[self.viewList objectAtIndex:kIASKSpecifierValuesViewControllerIndex] objectForKey:@"viewController"];
if (targetViewController == nil) {
// the view controller has not been created yet, create it and set it to our viewList array
// create a new dictionary with the new view controller
NSMutableDictionary *newItemDict = [NSMutableDictionary dictionaryWithCapacity:3];
[newItemDict addEntriesFromDictionary: [self.viewList objectAtIndex:kIASKSpecifierValuesViewControllerIndex]]; // copy the title and explain strings
targetViewController = [[IASKSpecifierValuesViewController alloc] initWithNibName:@"IASKSpecifierValuesView" bundle:nil];
// add the new view controller to the dictionary and then to the 'viewList' array
[newItemDict setObject:targetViewController forKey:@"viewController"];
[self.viewList replaceObjectAtIndex:kIASKSpecifierValuesViewControllerIndex withObject:newItemDict];
[targetViewController release];
// load the view controll back in to push it
targetViewController = [[self.viewList objectAtIndex:kIASKSpecifierValuesViewControllerIndex] objectForKey:@"viewController"];
}
self.currentIndexPath = indexPath;
[targetViewController setCurrentSpecifier:specifier];
targetViewController.settingsReader = self.settingsReader;
targetViewController.settingsStore = self.settingsStore;
[[self navigationController] pushViewController:targetViewController animated:YES];
}
else if ([[specifier type] isEqualToString:kIASKPSSliderSpecifier]) {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
else if ([[specifier type] isEqualToString:kIASKPSTextFieldSpecifier]) {
IASKPSTextFieldSpecifierViewCell *textFieldCell = (id)[tableView cellForRowAtIndexPath:indexPath];
[textFieldCell.textField becomeFirstResponder];
}
else if ([[specifier type] isEqualToString:kIASKPSChildPaneSpecifier]) {
Class vcClass = [specifier viewControllerClass];
if (vcClass) {
SEL initSelector = [specifier viewControllerSelector];
if (!initSelector) {
initSelector = @selector(init);
}
UIViewController * vc = [vcClass performSelector:@selector(alloc)];
[vc performSelector:initSelector withObject:[specifier file] withObject:[specifier key]];
if ([vc respondsToSelector:@selector(setDelegate:)]) {
[vc performSelector:@selector(setDelegate:) withObject:self.delegate];
}
if ([vc respondsToSelector:@selector(setSettingsStore:)]) {
[vc performSelector:@selector(setSettingsStore:) withObject:self.settingsStore];
}
self.navigationController.delegate = nil;
[self.navigationController pushViewController:vc animated:YES];
[vc performSelector:@selector(release)];
return;
}
if (nil == [specifier file]) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
return;
}
IASKAppSettingsViewController *targetViewController = [[self.viewList objectAtIndex:kIASKSpecifierChildViewControllerIndex] objectForKey:@"viewController"];
if (targetViewController == nil) {
// the view controller has not been created yet, create it and set it to our viewList array
// create a new dictionary with the new view controller
NSMutableDictionary *newItemDict = [NSMutableDictionary dictionaryWithCapacity:3];
[newItemDict addEntriesFromDictionary: [self.viewList objectAtIndex:kIASKSpecifierChildViewControllerIndex]]; // copy the title and explain strings
targetViewController = [[[self class] alloc] initWithNibName:@"IASKAppSettingsView" bundle:nil];
targetViewController.showDoneButton = NO;
targetViewController.settingsStore = self.settingsStore;
targetViewController.delegate = self.delegate;
// add the new view controller to the dictionary and then to the 'viewList' array
[newItemDict setObject:targetViewController forKey:@"viewController"];
[self.viewList replaceObjectAtIndex:kIASKSpecifierChildViewControllerIndex withObject:newItemDict];
[targetViewController release];
// load the view controll back in to push it
targetViewController = [[self.viewList objectAtIndex:kIASKSpecifierChildViewControllerIndex] objectForKey:@"viewController"];
}
self.currentIndexPath = indexPath;
targetViewController.file = specifier.file;
targetViewController.title = specifier.title;
targetViewController.showCreditsFooter = NO;
[[self navigationController] pushViewController:targetViewController animated:YES];
} else if ([[specifier type] isEqualToString:kIASKOpenURLSpecifier]) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:specifier.file]];
} else if ([[specifier type] isEqualToString:kIASKButtonSpecifier]) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if ([self.delegate respondsToSelector:@selector(settingsViewController:buttonTappedForKey:)]) {
[self.delegate settingsViewController:self buttonTappedForKey:[specifier key]];
} else {
// legacy code, provided for backward compatibility
// the delegate mechanism above is much cleaner and doesn't leak
Class buttonClass = [specifier buttonClass];
SEL buttonAction = [specifier buttonAction];
if ([buttonClass respondsToSelector:buttonAction]) {
[buttonClass performSelector:buttonAction withObject:self withObject:[specifier key]];
NSLog(@"InAppSettingsKit Warning: Using IASKButtonSpecifier without implementing the delegate method is deprecated");
}
}
} /*else if ([[specifier type] isEqualToString:kIASKMailComposeSpecifier]) { //@iaskmail
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if ([MFMailComposeViewController canSendMail]) {
MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init];
mailViewController.navigationBar.barStyle = self.navigationController.navigationBar.barStyle;
mailViewController.navigationBar.tintColor = self.navigationController.navigationBar.tintColor;
if ([specifier localizedObjectForKey:kIASKMailComposeSubject]) {
[mailViewController setSubject:[specifier localizedObjectForKey:kIASKMailComposeSubject]];
}
if ([[specifier specifierDict] objectForKey:kIASKMailComposeToRecipents]) {
[mailViewController setToRecipients:[[specifier specifierDict] objectForKey:kIASKMailComposeToRecipents]];
}
if ([[specifier specifierDict] objectForKey:kIASKMailComposeCcRecipents]) {
[mailViewController setCcRecipients:[[specifier specifierDict] objectForKey:kIASKMailComposeCcRecipents]];
}
if ([[specifier specifierDict] objectForKey:kIASKMailComposeBccRecipents]) {
[mailViewController setBccRecipients:[[specifier specifierDict] objectForKey:kIASKMailComposeBccRecipents]];
}
if ([specifier localizedObjectForKey:kIASKMailComposeBody]) {
BOOL isHTML = NO;
if ([[specifier specifierDict] objectForKey:kIASKMailComposeBodyIsHTML]) {
isHTML = [[[specifier specifierDict] objectForKey:kIASKMailComposeBodyIsHTML] boolValue];
}
if ([self.delegate respondsToSelector:@selector(mailComposeBody)]) {
[mailViewController setMessageBody:[self.delegate mailComposeBody] isHTML:isHTML];
}
else {
[mailViewController setMessageBody:[specifier localizedObjectForKey:kIASKMailComposeBody] isHTML:isHTML];
}
}
UIViewController<MFMailComposeViewControllerDelegate> *vc = nil;
if ([self.delegate respondsToSelector:@selector(viewControllerForMailComposeView)]) {
vc = [self.delegate viewControllerForMailComposeView];
}
if (vc == nil) {
vc = self;
}
mailViewController.mailComposeDelegate = vc;
[vc presentModalViewController:mailViewController animated:YES];
[mailViewController release];
}else {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:NSLocalizedString(@"Mail not configured", @"InAppSettingsKit")
message:NSLocalizedString(@"This device is not configured for sending Email. Please configure the Mail settings in the Settings app.", @"InAppSettingsKit")
delegate: nil
cancelButtonTitle:NSLocalizedString(@"OK", @"InAppSettingsKit")
otherButtonTitles:nil];
[alert show];
[alert release];
}
} */ else {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
}
//#pragma mark -
//#pragma mark MFMailComposeViewControllerDelegate Function
//
//-(void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
//
// // Forward the mail compose delegate
// if ([self.delegate respondsToSelector:@selector(mailComposeController: didFinishWithResult: error:)]) {
// [self.delegate mailComposeController:controller didFinishWithResult:result error:error];
// }
//
// // NOTE: No error handling is done here
// [self dismissModalViewControllerAnimated:YES];
//}
#pragma mark -
#pragma mark UITextFieldDelegate Functions
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
self.currentFirstResponder = textField;
return YES;
}
- (void)_textChanged:(id)sender {
IASKTextField *text = (IASKTextField*)sender;
[_settingsStore setObject:[text text] forKey:[text key]];
[[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged
object:[text key]
userInfo:[NSDictionary dictionaryWithObject:[text text]
forKey:[text key]]];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
self.currentFirstResponder = nil;
return YES;
}
#pragma mark Notifications
- (void)synchronizeSettings {
[_settingsStore synchronize];
}
- (void)reload {
// wait 0.5 sec until UI is available after applicationWillEnterForeground
[self.tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.5];
}
#pragma mark CGRect Utility function
CGRect IASKCGRectSwap(CGRect rect) {
CGRect newRect;
newRect.origin.x = rect.origin.y;
newRect.origin.y = rect.origin.x;
newRect.size.width = rect.size.height;
newRect.size.height = rect.size.width;
return newRect;
}
@end

View File

@@ -0,0 +1,30 @@
//
// IASKAppSettingsWebViewController.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
@interface IASKAppSettingsWebViewController : UIViewController <UIWebViewDelegate/*, MFMailComposeViewControllerDelegate @iaskmail */ > {
UIWebView *webView;
NSURL *url;
}
- (id)initWithFile:(NSString*)htmlFileName key:(NSString*)key;
@property (nonatomic, retain) IBOutlet UIWebView *webView;
@property (nonatomic, retain) NSURL *url;
@end

View File

@@ -0,0 +1,150 @@
//
// IASKAppSettingsWebViewController.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKAppSettingsWebViewController.h"
@implementation IASKAppSettingsWebViewController
@synthesize url;
@synthesize webView;
- (id)initWithFile:(NSString*)urlString key:(NSString*)key {
if (!(self = [super initWithNibName:nil bundle:nil])) {
return nil;
}
self.url = [NSURL URLWithString:urlString];
if (!self.url || ![self.url scheme]) {
NSString *path = [[NSBundle mainBundle] pathForResource:[urlString stringByDeletingPathExtension] ofType:[urlString pathExtension]];
if(path)
self.url = [NSURL fileURLWithPath:path];
else
self.url = nil;
}
return self;
}
- (void)dealloc {
[webView release], webView = nil;
[url release], url = nil;
[super dealloc];
}
- (void)viewWillAppear:(BOOL)animated {
[webView loadRequest:[NSURLRequest requestWithURL:self.url]];
}
- (void)viewDidUnload {
[super viewDidUnload];
self.webView = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
self.navigationItem.title = [self.webView stringByEvaluatingJavaScriptFromString:@"document.title"];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *newURL = [request URL];
/* @iaskmail
// intercept mailto URL and send it to an in-app Mail compose view instead
if ([[newURL scheme] isEqualToString:@"mailto"]) {
NSArray *rawURLparts = [[newURL resourceSpecifier] componentsSeparatedByString:@"?"];
if (rawURLparts.count > 2) {
return NO; // invalid URL
}
MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init];
mailViewController.mailComposeDelegate = self;
NSMutableArray *toRecipients = [NSMutableArray array];
NSString *defaultRecipient = [rawURLparts objectAtIndex:0];
if (defaultRecipient.length) {
[toRecipients addObject:defaultRecipient];
}
if (rawURLparts.count == 2) {
NSString *queryString = [rawURLparts objectAtIndex:1];
NSArray *params = [queryString componentsSeparatedByString:@"&"];
for (NSString *param in params) {
NSArray *keyValue = [param componentsSeparatedByString:@"="];
if (keyValue.count != 2) {
continue;
}
NSString *key = [[keyValue objectAtIndex:0] lowercaseString];
NSString *value = [keyValue objectAtIndex:1];
value = (NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
(CFStringRef)value,
CFSTR(""),
kCFStringEncodingUTF8);
[value autorelease];
if ([key isEqualToString:@"subject"]) {
[mailViewController setSubject:value];
}
if ([key isEqualToString:@"body"]) {
[mailViewController setMessageBody:value isHTML:NO];
}
if ([key isEqualToString:@"to"]) {
[toRecipients addObjectsFromArray:[value componentsSeparatedByString:@","]];
}
if ([key isEqualToString:@"cc"]) {
NSArray *recipients = [value componentsSeparatedByString:@","];
[mailViewController setCcRecipients:recipients];
}
if ([key isEqualToString:@"bcc"]) {
NSArray *recipients = [value componentsSeparatedByString:@","];
[mailViewController setBccRecipients:recipients];
}
}
}
[mailViewController setToRecipients:toRecipients];
[self presentModalViewController:mailViewController animated:YES];
[mailViewController release];
return NO;
}
*/
// open inline if host is the same, otherwise, pass to the system
if (![newURL host] || [[newURL host] isEqualToString:[self.url host]]) {
return YES;
}
[[UIApplication sharedApplication] openURL:newURL];
return NO;
}
//- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
// [self dismissModalViewControllerAnimated:YES];
//}
@end

View File

@@ -0,0 +1,38 @@
//
// IASKSpecifierValuesViewController.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <UIKit/UIKit.h>
#import "IASKSettingsStore.h"
@class IASKSpecifier;
@class IASKSettingsReader;
@interface IASKSpecifierValuesViewController : UIViewController {
UITableView *_tableView;
IASKSpecifier *_currentSpecifier;
NSIndexPath *_checkedItem;
IASKSettingsReader *_settingsReader;
id<IASKSettingsStore> _settingsStore;
}
@property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) NSIndexPath *checkedItem;
@property (nonatomic, retain) IASKSpecifier *currentSpecifier;
@property (nonatomic, retain) IASKSettingsReader *settingsReader;
@property (nonatomic, retain) id<IASKSettingsStore> settingsStore;
@end

View File

@@ -0,0 +1,196 @@
//
// IASKSpecifierValuesViewController.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKSpecifierValuesViewController.h"
#import "IASKSpecifier.h"
#import "IASKSettingsReader.h"
#import "IASKSettingsStoreUserDefaults.h"
#define kCellValue @"kCellValue"
@interface IASKSpecifierValuesViewController()
- (void)userDefaultsDidChange;
@end
@implementation IASKSpecifierValuesViewController
@synthesize tableView=_tableView;
@synthesize currentSpecifier=_currentSpecifier;
@synthesize checkedItem=_checkedItem;
@synthesize settingsReader = _settingsReader;
@synthesize settingsStore = _settingsStore;
- (void) updateCheckedItem {
NSInteger index;
// Find the currently checked item
if([self.settingsStore objectForKey:[_currentSpecifier key]]) {
index = [[_currentSpecifier multipleValues] indexOfObject:[self.settingsStore objectForKey:[_currentSpecifier key]]];
} else {
index = [[_currentSpecifier multipleValues] indexOfObject:[_currentSpecifier defaultValue]];
}
[self setCheckedItem:[NSIndexPath indexPathForRow:index inSection:0]];
}
- (id<IASKSettingsStore>)settingsStore {
if(_settingsStore == nil) {
_settingsStore = [[IASKSettingsStoreUserDefaults alloc] init];
}
return _settingsStore;
}
- (void)viewWillAppear:(BOOL)animated {
if (_currentSpecifier) {
[self setTitle:[_currentSpecifier title]];
[self updateCheckedItem];
}
if (_tableView) {
[_tableView reloadData];
// Make sure the currently checked item is visible
[_tableView scrollToRowAtIndexPath:[self checkedItem] atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
}
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[_tableView flashScrollIndicators];
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(userDefaultsDidChange)
name:NSUserDefaultsDidChangeNotification
object:[NSUserDefaults standardUserDefaults]];
}
- (void)viewDidDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:nil];
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.tableView = nil;
}
- (void)dealloc {
[_currentSpecifier release], _currentSpecifier = nil;
[_checkedItem release], _checkedItem = nil;
[_settingsReader release], _settingsReader = nil;
[_settingsStore release], _settingsStore = nil;
[_tableView release], _tableView = nil;
[super dealloc];
}
#pragma mark -
#pragma mark UITableView delegates
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_currentSpecifier multipleValuesCount];
}
- (void)selectCell:(UITableViewCell *)cell {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
[[cell textLabel] setTextColor:kIASKgrayBlueColor];
}
- (void)deselectCell:(UITableViewCell *)cell {
[cell setAccessoryType:UITableViewCellAccessoryNone];
[[cell textLabel] setTextColor:[UIColor darkTextColor]];
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
return [_currentSpecifier footerText];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellValue];
NSArray *titles = [_currentSpecifier multipleTitles];
if (!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellValue] autorelease];
cell.backgroundColor = [UIColor whiteColor];
}
if ([indexPath isEqual:[self checkedItem]]) {
[self selectCell:cell];
} else {
[self deselectCell:cell];
}
@try {
[[cell textLabel] setText:[self.settingsReader titleForStringId:[titles objectAtIndex:indexPath.row]]];
}
@catch (NSException * e) {}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath == [self checkedItem]) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
return;
}
NSArray *values = [_currentSpecifier multipleValues];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self deselectCell:[tableView cellForRowAtIndexPath:[self checkedItem]]];
[self selectCell:[tableView cellForRowAtIndexPath:indexPath]];
[self setCheckedItem:indexPath];
[self.settingsStore setObject:[values objectAtIndex:indexPath.row] forKey:[_currentSpecifier key]];
[self.settingsStore synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged
object:[_currentSpecifier key]
userInfo:[NSDictionary dictionaryWithObject:[values objectAtIndex:indexPath.row]
forKey:[_currentSpecifier key]]];
}
#pragma mark Notifications
- (void)userDefaultsDidChange {
NSIndexPath *oldCheckedItem = self.checkedItem;
if(_currentSpecifier) {
[self updateCheckedItem];
}
// only reload the table if it had changed; prevents animation cancellation
if (self.checkedItem != oldCheckedItem) {
[_tableView reloadData];
}
}
@end

View File

@@ -0,0 +1,146 @@
//
// IASKSettingsReader.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <Foundation/Foundation.h>
#define kIASKPreferenceSpecifiers @"PreferenceSpecifiers"
#define kIASKType @"Type"
#define kIASKTitle @"Title"
#define kIASKFooterText @"FooterText"
#define kIASKKey @"Key"
#define kIASKFile @"File"
#define kIASKDefaultValue @"DefaultValue"
#define kIASKMinimumValue @"MinimumValue"
#define kIASKMaximumValue @"MaximumValue"
#define kIASKTextOffsetPixels @"TextOffsetPixels"
#define kIASKTrueValue @"TrueValue"
#define kIASKFalseValue @"FalseValue"
#define kIASKIsSecure @"IsSecure"
#define KIASKKeyboardType @"KeyboardType"
#define kIASKAutocapitalizationType @"AutocapitalizationType"
#define kIASKAutoCorrectionType @"AutocorrectionType"
#define kIASKValues @"Values"
#define kIASKTitles @"Titles"
#define kIASKViewControllerClass @"IASKViewControllerClass"
#define kIASKViewControllerSelector @"IASKViewControllerSelector"
#define kIASKButtonClass @"IASKButtonClass"
#define kIASKButtonAction @"IASKButtonAction"
#define kIASKMailComposeToRecipents @"IASKMailComposeToRecipents"
#define kIASKMailComposeCcRecipents @"IASKMailComposeCcRecipents"
#define kIASKMailComposeBccRecipents @"IASKMailComposeBccRecipents"
#define kIASKMailComposeSubject @"IASKMailComposeSubject"
#define kIASKMailComposeBody @"IASKMailComposeBody"
#define kIASKMailComposeBodyIsHTML @"IASKMailComposeBodyIsHTML"
#define kIASKKeyboardAlphabet @"Alphabet"
#define kIASKKeyboardASCII @"Ascii"
#define kIASKKeyboardNumbersAndPunctuation @"NumbersAndPunctuation"
#define kIASKKeyboardNumberPad @"NumberPad"
#define kIASKKeyboardDecimalPad @"DecimalPad"
#define KIASKKeyboardURL @"URL"
#define kIASKKeyboardEmailAddress @"EmailAddress"
#define kIASKAutoCapNone @"None"
#define kIASKAutoCapSentences @"Sentences"
#define kIASKAutoCapWords @"Words"
#define kIASKAutoCapAllCharacters @"AllCharacters"
#define kIASKAutoCorrDefault @"Default"
#define kIASKAutoCorrNo @"No"
#define kIASKAutoCorrYes @"Yes"
#define kIASKMinimumValueImage @"MinimumValueImage"
#define kIASKMaximumValueImage @"MaximumValueImage"
#define kIASKStringIfEmpty @"StringIfEmpty"
#define kIASKPSGroupSpecifier @"PSGroupSpecifier"
#define kIASKPSToggleSwitchSpecifier @"PSToggleSwitchSpecifier"
#define kIASKPSMultiValueSpecifier @"PSMultiValueSpecifier"
#define kIASKPSSliderSpecifier @"PSSliderSpecifier"
#define kIASKPSTitleValueSpecifier @"PSTitleValueSpecifier"
#define kIASKPSTextFieldSpecifier @"PSTextFieldSpecifier"
#define kIASKPSChildPaneSpecifier @"PSChildPaneSpecifier"
#define kIASKOpenURLSpecifier @"IASKOpenURLSpecifier"
#define kIASKButtonSpecifier @"IASKButtonSpecifier"
#define kIASKMailComposeSpecifier @"IASKMailComposeSpecifier"
#define kIASKCustomViewSpecifier @"IASKCustomViewSpecifier"
#define kIASKBundleFolder @"Settings.bundle"
#define kIASKBundleFolderAlt @"InAppSettings.bundle"
#define kIASKBundleFilename @"Root.plist"
#define KIASKBundleLocaleFolderExtension @".lproj"
#define kIASKAppSettingChanged @"kAppSettingChanged"
#define kIASKSectionHeaderIndex 0
#define kIASKSliderNoImagesPadding 11
#define kIASKSliderImagesPadding 43
#define kIASKTableWidth 320
#define kIASKSpacing 5
#define kIASKMinLabelWidth 97
#define kIASKMinValueWidth 35
#define kIASKPaddingLeft 9
#define kIASKPaddingRight 10
#define kIASKHorizontalPaddingGroupTitles 19
#define kIASKVerticalPaddingGroupTitles 15
#define kIASKLabelFontSize 17
#define kIASKgrayBlueColor [UIColor colorWithRed:0.318 green:0.4 blue:0.569 alpha:1.0]
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32
#endif
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_1
#define kCFCoreFoundationVersionNumber_iPhoneOS_4_1 550.38
#endif
#define IASK_IF_IOS4_OR_GREATER(...) \
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \
{ \
__VA_ARGS__ \
}
@class IASKSpecifier;
@interface IASKSettingsReader : NSObject {
NSString *_path;
NSString *_localizationTable;
NSString *_bundlePath;
NSDictionary *_settingsBundle;
NSArray *_dataSource;
NSBundle *_bundle;
}
- (id)initWithFile:(NSString*)file;
- (NSInteger)numberOfSections;
- (NSInteger)numberOfRowsForSection:(NSInteger)section;
- (IASKSpecifier*)specifierForIndexPath:(NSIndexPath*)indexPath;
- (IASKSpecifier*)specifierForKey:(NSString*)key;
- (NSString*)titleForSection:(NSInteger)section;
- (NSString*)keyForSection:(NSInteger)section;
- (NSString*)footerTextForSection:(NSInteger)section;
- (NSString*)titleForStringId:(NSString*)stringId;
- (NSString*)pathForImageNamed:(NSString*)image;
@property (nonatomic, retain) NSString *path;
@property (nonatomic, retain) NSString *localizationTable;
@property (nonatomic, retain) NSString *bundlePath;
@property (nonatomic, retain) NSDictionary *settingsBundle;
@property (nonatomic, retain) NSArray *dataSource;
@end

View File

@@ -0,0 +1,259 @@
//
// IASKSettingsReader.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKSettingsReader.h"
#import "IASKSpecifier.h"
@interface IASKSettingsReader (private)
- (void)_reinterpretBundle:(NSDictionary*)settingsBundle;
- (BOOL)_sectionHasHeading:(NSInteger)section;
- (NSString *)platformSuffix;
- (NSString *)locateSettingsFile:(NSString *)file;
@end
@implementation IASKSettingsReader
@synthesize path=_path,
localizationTable=_localizationTable,
bundlePath=_bundlePath,
settingsBundle=_settingsBundle,
dataSource=_dataSource;
- (id)init {
return [self initWithFile:@"Root"];
}
- (id)initWithFile:(NSString*)file {
if ((self=[super init])) {
self.path = [self locateSettingsFile: file];
[self setSettingsBundle:[NSDictionary dictionaryWithContentsOfFile:self.path]];
self.bundlePath = [self.path stringByDeletingLastPathComponent];
_bundle = [[NSBundle bundleWithPath:[self bundlePath]] retain];
// Look for localization file
self.localizationTable = [self.settingsBundle objectForKey:@"StringsTable"];
NSLog(@"Loc table: %@. bundle: %@ (%p)\n", self.localizationTable, _bundlePath, _bundle);
if (!self.localizationTable)
{
// Look for localization file using filename
self.localizationTable = [[[[self.path stringByDeletingPathExtension] // removes '.plist'
stringByDeletingPathExtension] // removes potential '.inApp'
lastPathComponent] // strip absolute path
stringByReplacingOccurrencesOfString:[self platformSuffix] withString:@""]; // removes potential '~device' (~ipad, ~iphone)
NSLog(@"Settings string filename: %@\n", self.localizationTable);
if([_bundle pathForResource:self.localizationTable ofType:@"strings"] == nil){
// Could not find the specified localization: use default
self.localizationTable = @"Root";
NSLog(@"Couldn't find file though..\n");
}
}
if (_settingsBundle) {
[self _reinterpretBundle:_settingsBundle];
}
}
return self;
}
- (void)dealloc {
[_path release], _path = nil;
[_localizationTable release], _localizationTable = nil;
[_bundlePath release], _bundlePath = nil;
[_settingsBundle release], _settingsBundle = nil;
[_dataSource release], _dataSource = nil;
[_bundle release], _bundle = nil;
[super dealloc];
}
- (void)_reinterpretBundle:(NSDictionary*)settingsBundle {
NSArray *preferenceSpecifiers = [settingsBundle objectForKey:kIASKPreferenceSpecifiers];
NSInteger sectionCount = -1;
NSMutableArray *dataSource = [[[NSMutableArray alloc] init] autorelease];
for (NSDictionary *specifier in preferenceSpecifiers) {
if ([(NSString*)[specifier objectForKey:kIASKType] isEqualToString:kIASKPSGroupSpecifier]) {
NSMutableArray *newArray = [[NSMutableArray alloc] init];
[newArray addObject:specifier];
[dataSource addObject:newArray];
[newArray release];
sectionCount++;
}
else {
if (sectionCount == -1) {
NSMutableArray *newArray = [[NSMutableArray alloc] init];
[dataSource addObject:newArray];
[newArray release];
sectionCount++;
}
IASKSpecifier *newSpecifier = [[IASKSpecifier alloc] initWithSpecifier:specifier];
[(NSMutableArray*)[dataSource objectAtIndex:sectionCount] addObject:newSpecifier];
[newSpecifier release];
}
}
[self setDataSource:dataSource];
}
- (BOOL)_sectionHasHeading:(NSInteger)section {
return [[[[self dataSource] objectAtIndex:section] objectAtIndex:0] isKindOfClass:[NSDictionary class]];
}
- (NSInteger)numberOfSections {
return [[self dataSource] count];
}
- (NSInteger)numberOfRowsForSection:(NSInteger)section {
int headingCorrection = [self _sectionHasHeading:section] ? 1 : 0;
return [(NSArray*)[[self dataSource] objectAtIndex:section] count] - headingCorrection;
}
- (IASKSpecifier*)specifierForIndexPath:(NSIndexPath*)indexPath {
int headingCorrection = [self _sectionHasHeading:indexPath.section] ? 1 : 0;
IASKSpecifier *specifier = [[[self dataSource] objectAtIndex:indexPath.section] objectAtIndex:(indexPath.row+headingCorrection)];
specifier.settingsReader = self;
return specifier;
}
- (IASKSpecifier*)specifierForKey:(NSString*)key {
for (NSArray *specifiers in _dataSource) {
for (id sp in specifiers) {
if ([sp isKindOfClass:[IASKSpecifier class]]) {
if ([[sp key] isEqualToString:key]) {
return sp;
}
}
}
}
return nil;
}
- (NSString*)titleForSection:(NSInteger)section {
if ([self _sectionHasHeading:section]) {
NSDictionary *dict = [[[self dataSource] objectAtIndex:section] objectAtIndex:kIASKSectionHeaderIndex];
return [_bundle localizedStringForKey:[dict objectForKey:kIASKTitle] value:[dict objectForKey:kIASKTitle] table:self.localizationTable];
}
return nil;
}
- (NSString*)keyForSection:(NSInteger)section {
if ([self _sectionHasHeading:section]) {
return [[[[self dataSource] objectAtIndex:section] objectAtIndex:kIASKSectionHeaderIndex] objectForKey:kIASKKey];
}
return nil;
}
- (NSString*)footerTextForSection:(NSInteger)section {
if ([self _sectionHasHeading:section]) {
NSDictionary *dict = [[[self dataSource] objectAtIndex:section] objectAtIndex:kIASKSectionHeaderIndex];
return [_bundle localizedStringForKey:[dict objectForKey:kIASKFooterText] value:[dict objectForKey:kIASKFooterText] table:self.localizationTable];
}
return nil;
}
- (NSString*)titleForStringId:(NSString*)stringId {
return [_bundle localizedStringForKey:stringId value:stringId table:self.localizationTable];
}
- (NSString*)pathForImageNamed:(NSString*)image {
return [[self bundlePath] stringByAppendingPathComponent:image];
}
- (NSString *)platformSuffix {
BOOL isPad = NO;
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 30200)
isPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
#endif
return isPad ? @"~ipad" : @"~iphone";
}
- (NSString *)file:(NSString *)file
withBundle:(NSString *)bundle
suffix:(NSString *)suffix
extension:(NSString *)extension {
NSString *appBundle = [[NSBundle mainBundle] bundlePath];
bundle = [appBundle stringByAppendingPathComponent:bundle];
file = [file stringByAppendingFormat:@"%@%@", suffix, extension];
return [bundle stringByAppendingPathComponent:file];
}
- (NSString *)locateSettingsFile: (NSString *)file {
// The file is searched in the following order:
//
// InAppSettings.bundle/FILE~DEVICE.inApp.plist
// InAppSettings.bundle/FILE.inApp.plist
// InAppSettings.bundle/FILE~DEVICE.plist
// InAppSettings.bundle/FILE.plist
// Settings.bundle/FILE~DEVICE.inApp.plist
// Settings.bundle/FILE.inApp.plist
// Settings.bundle/FILE~DEVICE.plist
// Settings.bundle/FILE.plist
//
// where DEVICE is either "iphone" or "ipad" depending on the current
// interface idiom.
//
// Settings.app uses the ~DEVICE suffixes since iOS 4.0. There are some
// differences from this implementation:
// - For an iPhone-only app running on iPad, Settings.app will not use the
// ~iphone suffix. There is no point in using these suffixes outside
// of universal apps anyway.
// - This implementation uses the device suffixes on iOS 3.x as well.
// - also check current locale (short only)
NSArray *bundles =
[NSArray arrayWithObjects:kIASKBundleFolderAlt, kIASKBundleFolder, nil];
NSArray *extensions =
[NSArray arrayWithObjects:@".inApp.plist", @".plist", nil];
NSArray *suffixes =
[NSArray arrayWithObjects:[self platformSuffix], @"", nil];
NSArray *languages =
[NSArray arrayWithObjects:[[[NSLocale preferredLanguages] objectAtIndex:0] stringByAppendingString:KIASKBundleLocaleFolderExtension], @"", nil];
NSString *path = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
for (NSString *bundle in bundles) {
for (NSString *extension in extensions) {
for (NSString *suffix in suffixes) {
for (NSString *language in languages) {
path = [self file:file
withBundle:[bundle stringByAppendingPathComponent:language]
suffix:suffix
extension:extension];
if ([fileManager fileExistsAtPath:path]) {
goto exitFromNestedLoop;
}
}
}
}
}
exitFromNestedLoop:
return path;
}
@end

View File

@@ -0,0 +1,39 @@
//
// IASKSettingsStore.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <Foundation/Foundation.h>
@protocol IASKSettingsStore <NSObject>
@required
- (void)setBool:(BOOL)value forKey:(NSString*)key;
- (void)setFloat:(float)value forKey:(NSString*)key;
- (void)setDouble:(double)value forKey:(NSString*)key;
- (void)setInteger:(int)value forKey:(NSString*)key;
- (void)setObject:(id)value forKey:(NSString*)key;
- (BOOL)boolForKey:(NSString*)key;
- (float)floatForKey:(NSString*)key;
- (double)doubleForKey:(NSString*)key;
- (int)integerForKey:(NSString*)key;
- (id)objectForKey:(NSString*)key;
- (BOOL)synchronize; // Write settings to a permanant storage. Returns YES on success, NO otherwise
@end
@interface IASKAbstractSettingsStore : NSObject <IASKSettingsStore> {
}
@end

View File

@@ -0,0 +1,68 @@
//
// IASKSettingsStore.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKSettingsStore.h"
@implementation IASKAbstractSettingsStore
- (void)setObject:(id)value forKey:(NSString*)key {
[NSException raise:@"Unimplemented"
format:@"setObject:forKey: must be implemented in subclasses of IASKAbstractSettingsStore"];
}
- (id)objectForKey:(NSString*)key {
[NSException raise:@"Unimplemented"
format:@"objectForKey: must be implemented in subclasses of IASKAbstractSettingsStore"];
return nil;
}
- (void)setBool:(BOOL)value forKey:(NSString*)key {
[self setObject:[NSNumber numberWithBool:value] forKey:key];
}
- (void)setFloat:(float)value forKey:(NSString*)key {
[self setObject:[NSNumber numberWithFloat:value] forKey:key];
}
- (void)setInteger:(int)value forKey:(NSString*)key {
[self setObject:[NSNumber numberWithInt:value] forKey:key];
}
- (void)setDouble:(double)value forKey:(NSString*)key {
[self setObject:[NSNumber numberWithDouble:value] forKey:key];
}
- (BOOL)boolForKey:(NSString*)key {
return [[self objectForKey:key] boolValue];
}
- (float)floatForKey:(NSString*)key {
return [[self objectForKey:key] floatValue];
}
- (int)integerForKey:(NSString*)key {
return [[self objectForKey:key] intValue];
}
- (double)doubleForKey:(NSString*)key {
return [[self objectForKey:key] doubleValue];
}
- (BOOL)synchronize {
return NO;
}
@end

View File

@@ -0,0 +1,28 @@
//
// IASKSettingsStoreFile.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <Foundation/Foundation.h>
#import "IASKSettingsStore.h"
@interface IASKSettingsStoreFile : IASKAbstractSettingsStore {
NSString * _filePath;
NSMutableDictionary * _dict;
}
- (id)initWithPath:(NSString*)path;
@end

View File

@@ -0,0 +1,54 @@
//
// IASKSettingsStoreFile.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKSettingsStoreFile.h"
@implementation IASKSettingsStoreFile
- (id)initWithPath:(NSString*)path {
if((self = [super init])) {
_filePath = [path retain];
_dict = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
if(_dict == nil) {
_dict = [[NSMutableDictionary alloc] init];
}
}
return self;
}
- (void)dealloc {
[_dict release], _dict = nil;
[_filePath release], _filePath = nil;
[super dealloc];
}
- (void)setObject:(id)value forKey:(NSString *)key {
[_dict setObject:value forKey:key];
}
- (id)objectForKey:(NSString *)key {
return [_dict objectForKey:key];
}
- (BOOL)synchronize {
return [_dict writeToFile:_filePath atomically:YES];
}
@end

View File

@@ -0,0 +1,25 @@
//
// IASKSettingsStoreUserDefaults.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <Foundation/Foundation.h>
#import "IASKSettingsStore.h"
@interface IASKSettingsStoreUserDefaults : IASKAbstractSettingsStore {
}
@end

View File

@@ -0,0 +1,67 @@
//
// IASKSettingsStoreUserDefaults.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// Marc-Etienne M.Léveillé, Edovia Inc., http://www.edovia.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKSettingsStoreUserDefaults.h"
@implementation IASKSettingsStoreUserDefaults
- (void)setBool:(BOOL)value forKey:(NSString*)key {
[[NSUserDefaults standardUserDefaults] setBool:value forKey:key];
}
- (void)setFloat:(float)value forKey:(NSString*)key {
[[NSUserDefaults standardUserDefaults] setFloat:value forKey:key];
}
- (void)setDouble:(double)value forKey:(NSString*)key {
[[NSUserDefaults standardUserDefaults] setDouble:value forKey:key];
}
- (void)setInteger:(int)value forKey:(NSString*)key {
[[NSUserDefaults standardUserDefaults] setInteger:value forKey:key];
}
- (void)setObject:(id)value forKey:(NSString*)key {
[[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
}
- (BOOL)boolForKey:(NSString*)key {
return [[NSUserDefaults standardUserDefaults] boolForKey:key];
}
- (float)floatForKey:(NSString*)key {
return [[NSUserDefaults standardUserDefaults] floatForKey:key];
}
- (double)doubleForKey:(NSString*)key {
return [[NSUserDefaults standardUserDefaults] doubleForKey:key];
}
- (int)integerForKey:(NSString*)key {
return [[NSUserDefaults standardUserDefaults] integerForKey:key];
}
- (id)objectForKey:(NSString*)key {
return [[NSUserDefaults standardUserDefaults] objectForKey:key];
}
- (BOOL)synchronize {
return [[NSUserDefaults standardUserDefaults] synchronize];
}
@end

View File

@@ -0,0 +1,63 @@
//
// IASKSpecifier.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class IASKSettingsReader;
@interface IASKSpecifier : NSObject {
NSDictionary *_specifierDict;
NSDictionary *_multipleValuesDict;
IASKSettingsReader *_settingsReader;
}
@property (nonatomic, retain) NSDictionary *specifierDict;
@property (nonatomic, assign) IASKSettingsReader *settingsReader;
- (id)initWithSpecifier:(NSDictionary*)specifier;
- (NSString*)localizedObjectForKey:(NSString*)key;
- (NSString*)title;
- (NSString*)key;
- (NSString*)type;
- (NSString*)titleForCurrentValue:(id)currentValue;
- (NSInteger)multipleValuesCount;
- (NSArray*)multipleValues;
- (NSArray*)multipleTitles;
- (NSString*)file;
- (id)defaultValue;
- (id)defaultStringValue;
- (BOOL)defaultBoolValue;
- (id)trueValue;
- (id)falseValue;
- (float)minimumValue;
- (float)maximumValue;
- (int)textOffsetPixels;
- (NSString*)minimumValueImage;
- (NSString*)maximumValueImage;
- (BOOL)isSecure;
- (UIKeyboardType)keyboardType;
- (UITextAutocapitalizationType)autocapitalizationType;
- (UITextAutocorrectionType)autoCorrectionType;
- (NSString*)footerText;
- (NSString*)stringIfEmpty;
- (Class)viewControllerClass;
- (SEL)viewControllerSelector;
-(Class)buttonClass;
-(SEL)buttonAction;
-(BOOL)hasSpecified:(NSString*)key;
@end

View File

@@ -0,0 +1,256 @@
//
// IASKSpecifier.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKSpecifier.h"
#import "IASKSettingsReader.h"
@interface IASKSpecifier ()
@property (nonatomic, retain) NSDictionary *multipleValuesDict;
- (void)_reinterpretValues:(NSDictionary*)specifierDict;
@end
@implementation IASKSpecifier
@synthesize specifierDict=_specifierDict;
@synthesize multipleValuesDict=_multipleValuesDict;
@synthesize settingsReader = _settingsReader;
- (id)initWithSpecifier:(NSDictionary*)specifier {
if ((self = [super init])) {
[self setSpecifierDict:specifier];
if ([[self type] isEqualToString:kIASKPSMultiValueSpecifier] ||
[[self type] isEqualToString:kIASKPSTitleValueSpecifier]) {
[self _reinterpretValues:[self specifierDict]];
}
}
return self;
}
- (void)dealloc {
[_specifierDict release], _specifierDict = nil;
[_multipleValuesDict release], _multipleValuesDict = nil;
_settingsReader = nil;
[super dealloc];
}
-(BOOL)hasSpecified:(NSString*)key {
return [_specifierDict objectForKey:key] != nil;
}
- (void)_reinterpretValues:(NSDictionary*)specifierDict {
NSArray *values = [_specifierDict objectForKey:kIASKValues];
NSArray *titles = [_specifierDict objectForKey:kIASKTitles];
NSMutableDictionary *multipleValuesDict = [[[NSMutableDictionary alloc] init] autorelease];
if (values) {
[multipleValuesDict setObject:values forKey:kIASKValues];
}
if (titles) {
[multipleValuesDict setObject:titles forKey:kIASKTitles];
}
[self setMultipleValuesDict:multipleValuesDict];
}
- (NSString*)localizedObjectForKey:(NSString*)key {
return [self.settingsReader titleForStringId:[_specifierDict objectForKey:key]];
}
- (NSString*)title {
return [self localizedObjectForKey:kIASKTitle];
}
- (NSString*)footerText {
return [self localizedObjectForKey:kIASKFooterText];
}
- (NSString*)stringIfEmpty {
return [_specifierDict objectForKey:kIASKStringIfEmpty];
}
-(Class) viewControllerClass {
return NSClassFromString([_specifierDict objectForKey:kIASKViewControllerClass]);
}
-(SEL) viewControllerSelector {
return NSSelectorFromString([_specifierDict objectForKey:kIASKViewControllerSelector]);
}
-(Class)buttonClass {
return NSClassFromString([_specifierDict objectForKey:kIASKButtonClass]);
}
-(SEL)buttonAction {
return NSSelectorFromString([_specifierDict objectForKey:kIASKButtonAction]);
}
- (NSString*)key {
return [_specifierDict objectForKey:kIASKKey];
}
- (NSString*)type {
return [_specifierDict objectForKey:kIASKType];
}
- (NSString*)titleForCurrentValue:(id)currentValue {
NSArray *values = [self multipleValues];
NSArray *titles = [self multipleTitles];
if (values.count != titles.count) {
return nil;
}
NSInteger keyIndex = [values indexOfObject:currentValue];
if (keyIndex == NSNotFound) {
return nil;
}
@try {
return [self.settingsReader titleForStringId:[titles objectAtIndex:keyIndex]];
}
@catch (NSException * e) {}
return nil;
}
- (NSInteger)multipleValuesCount {
return [[_multipleValuesDict objectForKey:kIASKValues] count];
}
- (NSArray*)multipleValues {
return [_multipleValuesDict objectForKey:kIASKValues];
}
- (NSArray*)multipleTitles {
return [_multipleValuesDict objectForKey:kIASKTitles];
}
- (NSString*)file {
return [_specifierDict objectForKey:kIASKFile];
}
- (id)defaultValue {
return [_specifierDict objectForKey:kIASKDefaultValue];
}
- (id)defaultStringValue {
return [[_specifierDict objectForKey:kIASKDefaultValue] description];
}
- (BOOL)defaultBoolValue {
id defaultValue = [self defaultValue];
if ([defaultValue isEqual:[self trueValue]]) {
return YES;
}
if ([defaultValue isEqual:[self falseValue]]) {
return NO;
}
return [defaultValue boolValue];
}
- (id)trueValue {
return [_specifierDict objectForKey:kIASKTrueValue];
}
- (id)falseValue {
return [_specifierDict objectForKey:kIASKFalseValue];
}
- (float)minimumValue {
return [[_specifierDict objectForKey:kIASKMinimumValue] floatValue];
}
- (float)maximumValue {
return [[_specifierDict objectForKey:kIASKMaximumValue] floatValue];
}
- (int)textOffsetPixels {
return [[_specifierDict objectForKey:kIASKTextOffsetPixels] intValue];
}
- (NSString*)minimumValueImage {
return [_specifierDict objectForKey:kIASKMinimumValueImage];
}
- (NSString*)maximumValueImage {
return [_specifierDict objectForKey:kIASKMaximumValueImage];
}
- (BOOL)isSecure {
return [[_specifierDict objectForKey:kIASKIsSecure] boolValue];
}
- (UIKeyboardType)keyboardType {
if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardAlphabet]) {
return UIKeyboardTypeDefault;
}
else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardASCII]) {
return UIKeyboardTypeASCIICapable;
}
else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardNumbersAndPunctuation]) {
return UIKeyboardTypeNumbersAndPunctuation;
}
else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardNumberPad]) {
return UIKeyboardTypeNumberPad;
}
else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardDecimalPad]) {
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_1) {
return UIKeyboardTypeDecimalPad;
}
else {
return UIKeyboardTypeNumbersAndPunctuation;
}
}
else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:KIASKKeyboardURL]) {
return UIKeyboardTypeURL;
}
else if ([[_specifierDict objectForKey:KIASKKeyboardType] isEqualToString:kIASKKeyboardEmailAddress]) {
return UIKeyboardTypeEmailAddress;
}
return UIKeyboardTypeDefault;
}
- (UITextAutocapitalizationType)autocapitalizationType {
if ([[_specifierDict objectForKey:kIASKAutocapitalizationType] isEqualToString:kIASKAutoCapNone]) {
return UITextAutocapitalizationTypeNone;
}
else if ([[_specifierDict objectForKey:kIASKAutocapitalizationType] isEqualToString:kIASKAutoCapSentences]) {
return UITextAutocapitalizationTypeSentences;
}
else if ([[_specifierDict objectForKey:kIASKAutocapitalizationType] isEqualToString:kIASKAutoCapWords]) {
return UITextAutocapitalizationTypeWords;
}
else if ([[_specifierDict objectForKey:kIASKAutocapitalizationType] isEqualToString:kIASKAutoCapAllCharacters]) {
return UITextAutocapitalizationTypeAllCharacters;
}
return UITextAutocapitalizationTypeNone;
}
- (UITextAutocorrectionType)autoCorrectionType {
if ([[_specifierDict objectForKey:kIASKAutoCorrectionType] isEqualToString:kIASKAutoCorrDefault]) {
return UITextAutocorrectionTypeDefault;
}
else if ([[_specifierDict objectForKey:kIASKAutoCorrectionType] isEqualToString:kIASKAutoCorrNo]) {
return UITextAutocorrectionTypeNo;
}
else if ([[_specifierDict objectForKey:kIASKAutoCorrectionType] isEqualToString:kIASKAutoCorrYes]) {
return UITextAutocorrectionTypeYes;
}
return UITextAutocorrectionTypeDefault;
}
@end

View File

@@ -0,0 +1,38 @@
//
// IASKPSSliderSpecifierViewCell.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <UIKit/UIKit.h>
@class IASKSlider;
@interface IASKPSSliderSpecifierViewCell : UITableViewCell {
UILabel *_label;
IASKSlider *_slider;
UIImageView *_minImage;
UIImageView *_maxImage;
@public
float textOffsetPixels;
}
@property (nonatomic, assign) IBOutlet UILabel *label;
@property (nonatomic, assign) IBOutlet IASKSlider *slider;
@property (nonatomic, assign) IBOutlet UIImageView *minImage;
@property (nonatomic, assign) IBOutlet UIImageView *maxImage;
- (void) initDefaults;
@end

View File

@@ -0,0 +1,92 @@
//
// IASKPSSliderSpecifierViewCell.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2009-2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKPSSliderSpecifierViewCell.h"
#import "IASKSlider.h"
#import "IASKSettingsReader.h"
@implementation IASKPSSliderSpecifierViewCell
@synthesize label=_label,
slider=_slider,
minImage=_minImage,
maxImage=_maxImage;
- (void) initDefaults {
textOffsetPixels = -1;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect sliderBounds = _slider.bounds;
CGPoint sliderCenter = _slider.center;
double superViewWidth = _slider.superview.frame.size.width;
double centerOffset = 0;
_minImage.hidden = YES;
_maxImage.hidden = YES;
_label.hidden = YES;
if ([_label.text length] > 0) {
_label.hidden = NO;
if (textOffsetPixels < 0) {
CGSize size = [_label.text sizeWithFont:_label.font];
textOffsetPixels = size.width + 4;
NSLog(@"size: %f, %f\n", size.width, size.height);
}
superViewWidth -= textOffsetPixels;
centerOffset = textOffsetPixels;
}
sliderCenter.x = superViewWidth / 2 + centerOffset;
sliderBounds.size.width = superViewWidth - kIASKSliderNoImagesPadding * 2;
// Check if there are min and max images. If so, change the layout accordingly.
if (_minImage.image && _maxImage.image) {
// Both images
_minImage.hidden = NO;
_maxImage.hidden = NO;
sliderBounds.size.width = superViewWidth - kIASKSliderImagesPadding * 2;
}
else if (_minImage.image) {
// Min image
_minImage.hidden = NO;
sliderCenter.x += (kIASKSliderImagesPadding - kIASKSliderNoImagesPadding) / 2;
sliderBounds.size.width = superViewWidth - kIASKSliderNoImagesPadding - kIASKSliderImagesPadding;
}
else if (_maxImage.image) {
// Max image
_maxImage.hidden = NO;
sliderCenter.x -= (kIASKSliderImagesPadding - kIASKSliderNoImagesPadding) / 2;
sliderBounds.size.width = superViewWidth - kIASKSliderNoImagesPadding - kIASKSliderImagesPadding;
}
_slider.bounds = sliderBounds;
_slider.center = sliderCenter;
}
- (void)dealloc {
_minImage.image = nil;
_maxImage.image = nil;
[super dealloc];
}
- (void)prepareForReuse {
_minImage.image = nil;
_maxImage.image = nil;
}
@end

View File

@@ -0,0 +1,32 @@
//
// IASKPSTextFieldSpecifierViewCell.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <UIKit/UIKit.h>
@class IASKTextField;
@interface IASKPSTextFieldSpecifierViewCell : UITableViewCell<UITextFieldDelegate> {
UILabel *_label;
IASKTextField *_textField;
@public
NSString* stringIfEmpty;
}
@property (nonatomic, assign) IBOutlet UILabel *label;
@property (nonatomic, assign) IBOutlet IASKTextField *textField;
@end

View File

@@ -0,0 +1,76 @@
//
// IASKPSTextFieldSpecifierViewCell.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2009-2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKPSTextFieldSpecifierViewCell.h"
#import "IASKTextField.h"
#import "IASKSettingsReader.h"
@implementation IASKPSTextFieldSpecifierViewCell
@synthesize label=_label,
textField=_textField;
- (void)layoutSubviews {
[super layoutSubviews];
CGSize labelSize = [_label sizeThatFits:CGSizeZero];
labelSize.width = MIN(labelSize.width, _label.bounds.size.width);
CGRect textFieldFrame = _textField.frame;
textFieldFrame.origin.x = _label.frame.origin.x + MAX(kIASKMinLabelWidth, labelSize.width) + kIASKSpacing;
if (!_label.text.length)
textFieldFrame.origin.x = _label.frame.origin.x;
textFieldFrame.size.width = _textField.superview.frame.size.width - textFieldFrame.origin.x - _label.frame.origin.x;
_textField.frame = textFieldFrame;
[_textField setDelegate:self];
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSUInteger newLength = [textField.text length] + [string length] - range.length;
if (newLength > 24)
return NO;
int length = [string length];
for (int i = 0; i < length; ++i) {
unichar ch = [string characterAtIndex:i];
if (ch >= 128)
return NO;
}
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if (stringIfEmpty != nil && [[textField text] isEqualToString:@""]) {
[textField setText:stringIfEmpty];
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)dealloc {
[super dealloc];
}
@end

View File

@@ -0,0 +1,22 @@
//
// IASKPSTitleValueSpecifierViewCell.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <UIKit/UIKit.h>
@interface IASKPSTitleValueSpecifierViewCell : UITableViewCell
@end

View File

@@ -0,0 +1,54 @@
//
// IASKPSTitleValueSpecifierViewCell.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2010:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKPSTitleValueSpecifierViewCell.h"
#import "IASKSettingsReader.h"
@implementation IASKPSTitleValueSpecifierViewCell
- (void)layoutSubviews {
// left align the value if the title is empty
if (!self.textLabel.text.length) {
self.textLabel.text = self.detailTextLabel.text;
self.detailTextLabel.text = nil;
if ([self.reuseIdentifier isEqualToString:kIASKPSMultiValueSpecifier]) {
self.textLabel.font = [UIFont systemFontOfSize:[UIFont labelFontSize]];
self.textLabel.textColor = self.detailTextLabel.textColor;
}
}
[super layoutSubviews];
CGSize viewSize = [self.textLabel superview].frame.size;
// set the left title label frame
CGFloat labelWidth = [self.textLabel sizeThatFits:CGSizeZero].width;
CGFloat minValueWidth = (self.detailTextLabel.text.length) ? kIASKMinValueWidth + kIASKSpacing : 0;
labelWidth = MIN(labelWidth, viewSize.width - minValueWidth - kIASKPaddingLeft -kIASKPaddingRight);
CGRect labelFrame = CGRectMake(kIASKPaddingLeft, 0, labelWidth, viewSize.height -2);
self.textLabel.frame = labelFrame;
// set the right value label frame
if (self.detailTextLabel.text.length) {
CGRect valueFrame = CGRectMake(kIASKPaddingLeft + labelWidth + kIASKSpacing,
0,
viewSize.width - (kIASKPaddingLeft + labelWidth + kIASKSpacing) - kIASKPaddingRight,
viewSize.height -2);
self.detailTextLabel.frame = valueFrame;
}
}
@end

View File

@@ -0,0 +1,29 @@
//
// IASKPSToggleSwitchSpecifierViewCell.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <UIKit/UIKit.h>
@class IASKSwitch;
@interface IASKPSToggleSwitchSpecifierViewCell : UITableViewCell {
UILabel *_label;
IASKSwitch *_toggle;
}
@property (nonatomic, assign) IBOutlet UILabel *label;
@property (nonatomic, assign) IBOutlet IASKSwitch *toggle;
@end

View File

@@ -0,0 +1,46 @@
//
// IASKPSToggleSwitchSpecifierViewCell.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKPSToggleSwitchSpecifierViewCell.h"
#import "IASKSwitch.h"
@implementation IASKPSToggleSwitchSpecifierViewCell
@synthesize label=_label,
toggle=_toggle;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if ((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)dealloc {
[super dealloc];
}
@end

View File

@@ -0,0 +1,26 @@
//
// IASKSlider.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <UIKit/UIKit.h>
@interface IASKSlider : UISlider {
NSString *_key;
}
@property (nonatomic, retain) NSString *key;
@end

View File

@@ -0,0 +1,30 @@
//
// IASKSlider.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKSlider.h"
@implementation IASKSlider
@synthesize key=_key;
- (void)dealloc {
[_key release], _key = nil;
[super dealloc];
}
@end

View File

@@ -0,0 +1,26 @@
//
// IASKSwitch.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <UIKit/UIKit.h>
@interface IASKSwitch : UISwitch {
NSString *_key;
}
@property (nonatomic, retain) NSString *key;
@end

View File

@@ -0,0 +1,31 @@
//
// IASKSwitch.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKSwitch.h"
@implementation IASKSwitch
@synthesize key=_key;
- (void)dealloc {
[_key release], _key = nil;
[super dealloc];
}
@end

View File

@@ -0,0 +1,26 @@
//
// IASKTextField.h
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import <UIKit/UIKit.h>
@interface IASKTextField : UITextField {
NSString *_key;
}
@property (nonatomic, retain) NSString *key;
@end

View File

@@ -0,0 +1,30 @@
//
// IASKTextField.m
// http://www.inappsettingskit.com
//
// Copyright (c) 2009:
// Luc Vandal, Edovia Inc., http://www.edovia.com
// Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com
// All rights reserved.
//
// It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz,
// as the original authors of this code. You can give credit in a blog post, a tweet or on
// a info page of your app. Also, the original authors appreciate letting them know if you use this code.
//
// This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php
//
#import "IASKTextField.h"
@implementation IASKTextField
@synthesize key=_key;
- (void)dealloc {
[_key release], _key = nil;
[super dealloc];
}
@end

View File

@@ -0,0 +1,222 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1280</int>
<string key="IBDocument.SystemVersion">11B26</string>
<string key="IBDocument.InterfaceBuilderVersion">1934</string>
<string key="IBDocument.AppKitVersion">1138</string>
<string key="IBDocument.HIToolboxVersion">566.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">931</string>
</object>
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>IBProxyObject</string>
<string>IBUITableView</string>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBProxyObject" id="372490531">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBProxyObject" id="975951072">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUITableView" id="1838945">
<reference key="NSNextResponder"/>
<int key="NSvFlags">274</int>
<string key="NSFrameSize">{320, 460}</string>
<reference key="NSSuperview"/>
<reference key="NSNextKeyView"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">10</int>
<object class="NSImage" key="NSImage">
<int key="NSImageFlags">549453824</int>
<string key="NSSize">{512, 1}</string>
<object class="NSMutableArray" key="NSReps">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="0"/>
<object class="NSBitmapImageRep">
<object class="NSData" key="NSTIFFRepresentation">
<bytes key="NS.bytes">TU0AKgAACAjFzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/
y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/
xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/
xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/
xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/
xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/
xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/
y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/
y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/
xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/
xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/
xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/
xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/
xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/
y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/
y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/
xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/
xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/
xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/
xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/
xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/
y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/
y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/
xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/
xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/
xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/
xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/
xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/
y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/
y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/
xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/
xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/
xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/
xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/
xczS/8vS2P/L0tj/xczU/wANAQAAAwAAAAECAAAAAQEAAwAAAAEAAQAAAQIAAwAAAAQAAAiqAQMAAwAA
AAEAAQAAAQYAAwAAAAEAAgAAAREABAAAAAEAAAAIARIAAwAAAAEAAQAAARUAAwAAAAEABAAAARYAAwAA
AAEAAQAAARcABAAAAAEAAAgAARwAAwAAAAEAAQAAAVIAAwAAAAEAAQAAAVMAAwAAAAQAAAiyAAAAAAAI
AAgACAAIAAEAAQABAAE</bytes>
</object>
</object>
</object>
</object>
<object class="NSColor" key="NSColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MCAwAA</bytes>
</object>
</object>
<string key="IBUIColorCocoaTouchKeyPath">groupTableViewBackgroundColor</string>
</object>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBUIBouncesZoom">NO</bool>
<int key="IBUIStyle">1</int>
<int key="IBUISeparatorStyle">1</int>
<int key="IBUISectionIndexMinimumDisplayRowCount">0</int>
<bool key="IBUIShowsSelectionImmediatelyOnTouchBegin">YES</bool>
<float key="IBUIRowHeight">44</float>
<float key="IBUISectionHeaderHeight">10</float>
<float key="IBUISectionFooterHeight">10</float>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">view</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="1838945"/>
</object>
<int key="connectionID">10</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">dataSource</string>
<reference key="source" ref="1838945"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">6</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="1838945"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">7</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<object class="NSArray" key="object" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="372490531"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="975951072"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">4</int>
<reference key="object" ref="1838945"/>
<reference key="parent" ref="0"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-1.CustomClassName</string>
<string>-1.IBPluginDependency</string>
<string>-2.CustomClassName</string>
<string>-2.IBPluginDependency</string>
<string>4.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>IASKAppSettingsViewController</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>UIResponder</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="sourceID"/>
<int key="maxID">10</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes"/>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
<real value="1280" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
<integer value="3000" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">931</string>
</data>
</archive>

View File

@@ -0,0 +1,378 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1024</int>
<string key="IBDocument.SystemVersion">10F569</string>
<string key="IBDocument.InterfaceBuilderVersion">804</string>
<string key="IBDocument.AppKitVersion">1038.29</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">123</string>
</object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="1"/>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBProxyObject" id="372490531">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBProxyObject" id="975951072">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUIView" id="191373211">
<reference key="NSNextResponder"/>
<int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIWebView" id="197045155">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">274</int>
<string key="NSFrameSize">{320, 460}</string>
<reference key="NSSuperview" ref="191373211"/>
<object class="NSColor" key="IBUIBackgroundColor" id="697334121">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
</object>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
</object>
<string key="NSFrameSize">{320, 460}</string>
<reference key="NSSuperview"/>
<reference key="IBUIBackgroundColor" ref="697334121"/>
<object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics"/>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">view</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="191373211"/>
</object>
<int key="connectionID">3</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">webView</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="197045155"/>
</object>
<int key="connectionID">5</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="197045155"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">6</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<reference key="object" ref="0"/>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1</int>
<reference key="object" ref="191373211"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="197045155"/>
</object>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="372490531"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="975951072"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">4</int>
<reference key="object" ref="197045155"/>
<reference key="parent" ref="191373211"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-1.CustomClassName</string>
<string>-2.CustomClassName</string>
<string>1.IBEditorWindowLastContentRect</string>
<string>1.IBPluginDependency</string>
<string>4.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>IASKAppSettingsWebViewController</string>
<string>UIResponder</string>
<string>{{354, 412}, {320, 480}}</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<nil key="sourceID"/>
<int key="maxID">6</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">IASKAppSettingsWebViewController</string>
<string key="superclassName">UIViewController</string>
<object class="NSMutableDictionary" key="outlets">
<string key="NS.key.0">webView</string>
<string key="NS.object.0">UIWebView</string>
</object>
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
<string key="NS.key.0">webView</string>
<object class="IBToOneOutletInfo" key="NS.object.0">
<string key="name">webView</string>
<string key="candidateClassName">UIWebView</string>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">../InAppSettingsKit/Controllers/IASKAppSettingsWebViewController.h</string>
</object>
</object>
</object>
<object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSError.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSFileManager.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSKeyValueCoding.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSKeyValueObserving.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSKeyedArchiver.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSObject.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSRunLoop.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSThread.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSURL.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSURLConnection.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIAccessibility.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UINibLoading.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="842439622">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIResponder.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIResponder</string>
<string key="superclassName">NSObject</string>
<reference key="sourceIdentifier" ref="842439622"/>
</object>
<object class="IBPartialClassDescription">
<string key="className">UISearchBar</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UISearchBar.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UISearchDisplayController</string>
<string key="superclassName">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UISearchDisplayController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UITextField.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIView</string>
<string key="superclassName">UIResponder</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIView.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIViewController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UINavigationController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIViewController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIPopoverController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIViewController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UISplitViewController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIViewController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UITabBarController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIViewController</string>
<string key="superclassName">UIResponder</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIViewController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIWebView</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIWebView.h</string>
</object>
</object>
</object>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
<integer value="1024" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
<integer value="3100" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<string key="IBDocument.LastKnownRelativeProjectPath">../InAppSettingsKitSampleApp.xcodeproj</string>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">123</string>
</data>
</archive>

View File

@@ -0,0 +1,297 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1280</int>
<string key="IBDocument.SystemVersion">10K549</string>
<string key="IBDocument.InterfaceBuilderVersion">1938</string>
<string key="IBDocument.AppKitVersion">1038.36</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">933</string>
</object>
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>IBUISlider</string>
<string>IBUITableViewCell</string>
<string>IBUIImageView</string>
<string>IBUILabel</string>
<string>IBProxyObject</string>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBProxyObject" id="841351856">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBProxyObject" id="371349661">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUITableViewCell" id="699910302">
<reference key="NSNextResponder"/>
<int key="NSvFlags">290</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIView" id="833157797">
<reference key="NSNextResponder" ref="699910302"/>
<int key="NSvFlags">256</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUISlider" id="213296969">
<reference key="NSNextResponder" ref="833157797"/>
<int key="NSvFlags">290</int>
<string key="NSFrame">{{52, 12}, {216, 23}}</string>
<reference key="NSSuperview" ref="833157797"/>
<reference key="NSNextKeyView" ref="343121076"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIMultipleTouchEnabled">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIContentHorizontalAlignment">0</int>
<int key="IBUIContentVerticalAlignment">0</int>
<float key="IBUIValue">0.5</float>
</object>
<object class="IBUIImageView" id="343121076">
<reference key="NSNextResponder" ref="833157797"/>
<int key="NSvFlags">289</int>
<string key="NSFrame">{{289, 13}, {21, 21}}</string>
<reference key="NSSuperview" ref="833157797"/>
<reference key="NSNextKeyView"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentMode">8</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUIImageView" id="408162837">
<reference key="NSNextResponder" ref="833157797"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{10, 13}, {21, 21}}</string>
<reference key="NSSuperview" ref="833157797"/>
<reference key="NSNextKeyView" ref="100065965"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentMode">7</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUILabel" id="100065965">
<reference key="NSNextResponder" ref="833157797"/>
<int key="NSvFlags">294</int>
<string key="NSFrame">{{10, 13}, {240, 21}}</string>
<reference key="NSSuperview" ref="833157797"/>
<reference key="NSNextKeyView" ref="213296969"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText"/>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MCAwIDAAA</bytes>
</object>
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">1</int>
<float key="IBUIMinimumFontSize">10</float>
<object class="IBUIFontDescription" key="IBUIFontDescription">
<string key="name">Helvetica-Bold</string>
<string key="family">Helvetica</string>
<int key="traits">2</int>
<double key="pointSize">17</double>
</object>
<object class="NSFont" key="IBUIFont">
<string key="NSName">Helvetica-Bold</string>
<double key="NSSize">17</double>
<int key="NSfFlags">16</int>
</object>
</object>
</object>
<string key="NSFrameSize">{320, 43}</string>
<reference key="NSSuperview" ref="699910302"/>
<reference key="NSNextKeyView" ref="408162837"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MCAwAA</bytes>
</object>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<int key="IBUIContentMode">4</int>
<bool key="IBUIMultipleTouchEnabled">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
</object>
<string key="NSFrameSize">{320, 44}</string>
<reference key="NSSuperview"/>
<reference key="NSNextKeyView" ref="833157797"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MSAxIDEAA</bytes>
</object>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUISelectionStyle">0</int>
<reference key="IBUIContentView" ref="833157797"/>
<string key="IBUIReuseIdentifier">PSSliderSpecifier</string>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">slider</string>
<reference key="source" ref="699910302"/>
<reference key="destination" ref="213296969"/>
</object>
<int key="connectionID">12</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">maxImage</string>
<reference key="source" ref="699910302"/>
<reference key="destination" ref="343121076"/>
</object>
<int key="connectionID">13</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">minImage</string>
<reference key="source" ref="699910302"/>
<reference key="destination" ref="408162837"/>
</object>
<int key="connectionID">14</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">label</string>
<reference key="source" ref="699910302"/>
<reference key="destination" ref="100065965"/>
</object>
<int key="connectionID">18</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<object class="NSArray" key="object" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="841351856"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="371349661"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">2</int>
<reference key="object" ref="699910302"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="408162837"/>
<reference ref="343121076"/>
<reference ref="213296969"/>
<reference ref="100065965"/>
</object>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">3</int>
<reference key="object" ref="213296969"/>
<reference key="parent" ref="699910302"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">6</int>
<reference key="object" ref="343121076"/>
<reference key="parent" ref="699910302"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">9</int>
<reference key="object" ref="408162837"/>
<reference key="parent" ref="699910302"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">17</int>
<reference key="object" ref="100065965"/>
<reference key="parent" ref="699910302"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-1.IBPluginDependency</string>
<string>-2.CustomClassName</string>
<string>-2.IBPluginDependency</string>
<string>17.IBPluginDependency</string>
<string>2.CustomClassName</string>
<string>2.IBPluginDependency</string>
<string>3.CustomClassName</string>
<string>3.IBPluginDependency</string>
<string>6.IBPluginDependency</string>
<string>9.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>UIResponder</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>IASKPSSliderSpecifierViewCell</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>IASKSlider</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="sourceID"/>
<int key="maxID">18</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes"/>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
<real value="1280" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
<integer value="3100" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">933</string>
</data>
</archive>

View File

@@ -0,0 +1,262 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1280</int>
<string key="IBDocument.SystemVersion">10K549</string>
<string key="IBDocument.InterfaceBuilderVersion">1938</string>
<string key="IBDocument.AppKitVersion">1038.36</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">933</string>
</object>
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>IBUITableViewCell</string>
<string>IBUILabel</string>
<string>IBUITextField</string>
<string>IBProxyObject</string>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBProxyObject" id="841351856">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBProxyObject" id="371349661">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUITableViewCell" id="639928991">
<reference key="NSNextResponder"/>
<int key="NSvFlags">290</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIView" id="642554623">
<reference key="NSNextResponder" ref="639928991"/>
<int key="NSvFlags">256</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUILabel" id="68117349">
<reference key="NSNextResponder" ref="642554623"/>
<int key="NSvFlags">294</int>
<string key="NSFrame">{{9, 11}, {240, 21}}</string>
<reference key="NSSuperview" ref="642554623"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText">Label</string>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MCAwIDAAA</bytes>
</object>
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">1</int>
<float key="IBUIMinimumFontSize">10</float>
<object class="IBUIFontDescription" key="IBUIFontDescription">
<string key="name">Helvetica-Bold</string>
<string key="family">Helvetica</string>
<int key="traits">2</int>
<double key="pointSize">17</double>
</object>
<object class="NSFont" key="IBUIFont">
<string key="NSName">Helvetica-Bold</string>
<double key="NSSize">17</double>
<int key="NSfFlags">16</int>
</object>
</object>
<object class="IBUITextField" id="239084550">
<reference key="NSNextResponder" ref="642554623"/>
<int key="NSvFlags">291</int>
<string key="NSFrame">{{100, 11}, {201, 21}}</string>
<reference key="NSSuperview" ref="642554623"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText">hello</string>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MC4yNzQ1MDk4MiAwLjM3NjQ3MDYgMC41MjE1Njg2NgA</bytes>
</object>
<int key="IBUITextAlignment">2</int>
<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
<float key="IBUIMinimumFontSize">10</float>
<object class="IBUITextInputTraits" key="IBUITextInputTraits">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUIFontDescription" key="IBUIFontDescription">
<string key="name">Helvetica</string>
<string key="family">Helvetica</string>
<int key="traits">0</int>
<double key="pointSize">17</double>
</object>
<object class="NSFont" key="IBUIFont">
<string key="NSName">Helvetica</string>
<double key="NSSize">17</double>
<int key="NSfFlags">16</int>
</object>
</object>
</object>
<string key="NSFrameSize">{320, 43}</string>
<reference key="NSSuperview" ref="639928991"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MCAwAA</bytes>
</object>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<int key="IBUIContentMode">4</int>
<bool key="IBUIMultipleTouchEnabled">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
</object>
<string key="NSFrameSize">{320, 44}</string>
<reference key="NSSuperview"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MSAxIDEAA</bytes>
</object>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUISelectionStyle">0</int>
<reference key="IBUIContentView" ref="642554623"/>
<string key="IBUIReuseIdentifier">PSTextFieldSpecifier</string>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">label</string>
<reference key="source" ref="639928991"/>
<reference key="destination" ref="68117349"/>
</object>
<int key="connectionID">8</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">textField</string>
<reference key="source" ref="639928991"/>
<reference key="destination" ref="239084550"/>
</object>
<int key="connectionID">9</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="239084550"/>
<reference key="destination" ref="639928991"/>
</object>
<int key="connectionID">10</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<object class="NSArray" key="object" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="841351856"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="371349661"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">2</int>
<reference key="object" ref="639928991"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="239084550"/>
<reference ref="68117349"/>
</object>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">3</int>
<reference key="object" ref="68117349"/>
<reference key="parent" ref="639928991"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">4</int>
<reference key="object" ref="239084550"/>
<reference key="parent" ref="639928991"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-1.IBPluginDependency</string>
<string>-2.CustomClassName</string>
<string>-2.IBPluginDependency</string>
<string>2.CustomClassName</string>
<string>2.IBPluginDependency</string>
<string>3.IBPluginDependency</string>
<string>4.CustomClassName</string>
<string>4.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>UIResponder</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>IASKPSTextFieldSpecifierViewCell</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>IASKTextField</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="sourceID"/>
<int key="maxID">10</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes"/>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
<real value="1280" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
<integer value="3100" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">933</string>
</data>
</archive>

View File

@@ -0,0 +1,287 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">768</int>
<string key="IBDocument.SystemVersion">10J869</string>
<string key="IBDocument.InterfaceBuilderVersion">1306</string>
<string key="IBDocument.AppKitVersion">1038.35</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">301</string>
</object>
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>IBUITableViewCell</string>
<string>IBUISwitch</string>
<string>IBUILabel</string>
<string>IBProxyObject</string>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="dict.values" ref="0"/>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBProxyObject" id="841351856">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBProxyObject" id="371349661">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUITableViewCell" id="507262743">
<reference key="NSNextResponder"/>
<int key="NSvFlags">290</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIView" id="659685403">
<reference key="NSNextResponder" ref="507262743"/>
<int key="NSvFlags">256</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUILabel" id="697203718">
<reference key="NSNextResponder" ref="659685403"/>
<int key="NSvFlags">294</int>
<string key="NSFrame">{{9, 10}, {200, 21}}</string>
<reference key="NSSuperview" ref="659685403"/>
<reference key="NSWindow"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText">Label</string>
<object class="NSFont" key="IBUIFont">
<string key="NSName">Helvetica-Bold</string>
<double key="NSSize">17</double>
<int key="NSfFlags">16</int>
</object>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MCAwIDAAA</bytes>
</object>
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">1</int>
<float key="IBUIMinimumFontSize">10</float>
</object>
<object class="IBUISwitch" id="297989004">
<reference key="NSNextResponder" ref="659685403"/>
<int key="NSvFlags">289</int>
<string key="NSFrame">{{218, 8}, {94, 27}}</string>
<reference key="NSSuperview" ref="659685403"/>
<reference key="NSWindow"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIMultipleTouchEnabled">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIContentHorizontalAlignment">0</int>
<int key="IBUIContentVerticalAlignment">0</int>
<bool key="IBUIOn">YES</bool>
</object>
</object>
<string key="NSFrameSize">{320, 44}</string>
<reference key="NSSuperview" ref="507262743"/>
<reference key="NSWindow"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MCAwAA</bytes>
</object>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<int key="IBUIContentMode">4</int>
<bool key="IBUIMultipleTouchEnabled">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
</object>
<string key="NSFrameSize">{320, 44}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MSAxIDEAA</bytes>
</object>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUISeparatorStyle">1</int>
<int key="IBUISelectionStyle">0</int>
<reference key="IBUIContentView" ref="659685403"/>
<string key="IBUIReuseIdentifier">PSToggleSwitchSpecifier</string>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">toggle</string>
<reference key="source" ref="507262743"/>
<reference key="destination" ref="297989004"/>
</object>
<int key="connectionID">8</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">label</string>
<reference key="source" ref="507262743"/>
<reference key="destination" ref="697203718"/>
</object>
<int key="connectionID">9</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<reference key="object" ref="0"/>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="841351856"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="371349661"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">2</int>
<reference key="object" ref="507262743"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="697203718"/>
<reference ref="297989004"/>
</object>
<reference key="parent" ref="0"/>
<string key="objectName">Toggle Switch Specifier View Cell - PSToggleSwitchSpecifier</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">3</int>
<reference key="object" ref="697203718"/>
<reference key="parent" ref="507262743"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">4</int>
<reference key="object" ref="297989004"/>
<reference key="parent" ref="507262743"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-2.CustomClassName</string>
<string>2.CustomClassName</string>
<string>2.IBEditorWindowLastContentRect</string>
<string>2.IBPluginDependency</string>
<string>3.IBPluginDependency</string>
<string>4.CustomClassName</string>
<string>4.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>UIResponder</string>
<string>IASKPSToggleSwitchSpecifierViewCell</string>
<string>{{1676, 1200}, {320, 44}}</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>IASKSwitch</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="sourceID"/>
<int key="maxID">9</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">IASKPSToggleSwitchSpecifierViewCell</string>
<string key="superclassName">UITableViewCell</string>
<object class="NSMutableDictionary" key="outlets">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>label</string>
<string>toggle</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>UILabel</string>
<string>IASKSwitch</string>
</object>
</object>
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>label</string>
<string>toggle</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBToOneOutletInfo">
<string key="name">label</string>
<string key="candidateClassName">UILabel</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">toggle</string>
<string key="candidateClassName">IASKSwitch</string>
</object>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/IASKPSToggleSwitchSpecifierViewCell.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">IASKSwitch</string>
<string key="superclassName">UISwitch</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/IASKSwitch.h</string>
</object>
</object>
</object>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
<integer value="768" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
<integer value="3100" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">301</string>
</data>
</archive>

View File

@@ -0,0 +1,255 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">768</int>
<string key="IBDocument.SystemVersion">10J567</string>
<string key="IBDocument.InterfaceBuilderVersion">1294</string>
<string key="IBDocument.AppKitVersion">1038.35</string>
<string key="IBDocument.HIToolboxVersion">462.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">294</string>
</object>
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>IBProxyObject</string>
<string>IBUIView</string>
<string>IBUITableView</string>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="dict.values" ref="0"/>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBProxyObject" id="372490531">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBProxyObject" id="975951072">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUIView" id="191373211">
<reference key="NSNextResponder"/>
<int key="NSvFlags">292</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUITableView" id="575792171">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">274</int>
<string key="NSFrameSize">{320, 460}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">10</int>
<object class="NSImage" key="NSImage">
<int key="NSImageFlags">549453824</int>
<string key="NSSize">{84, 1}</string>
<object class="NSMutableArray" key="NSReps">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="0"/>
<object class="NSBitmapImageRep">
<object class="NSData" key="NSTIFFRepresentation">
<bytes key="NS.bytes">TU0AKgAAAVjFzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/
y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/
xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/
xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/
xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/
xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P8ADQEAAAMAAAABAFQAAAEB
AAMAAAABAAEAAAECAAMAAAAEAAAB+gEDAAMAAAABAAEAAAEGAAMAAAABAAIAAAERAAQAAAABAAAACAES
AAMAAAABAAEAAAEVAAMAAAABAAQAAAEWAAMAAAABAAEAAAEXAAQAAAABAAABUAEcAAMAAAABAAEAAAFS
AAMAAAABAAEAAAFTAAMAAAAEAAACAgAAAAAACAAIAAgACAABAAEAAQABA</bytes>
</object>
</object>
</object>
</object>
<object class="NSColor" key="NSColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MCAwAA</bytes>
</object>
</object>
<string key="IBUIColorCocoaTouchKeyPath">groupTableViewBackgroundColor</string>
</object>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBUIBouncesZoom">NO</bool>
<int key="IBUIStyle">1</int>
<int key="IBUISeparatorStyle">1</int>
<int key="IBUISectionIndexMinimumDisplayRowCount">0</int>
<bool key="IBUIShowsSelectionImmediatelyOnTouchBegin">YES</bool>
<float key="IBUIRowHeight">44</float>
<float key="IBUISectionHeaderHeight">10</float>
<float key="IBUISectionFooterHeight">10</float>
</object>
</object>
<string key="NSFrameSize">{320, 460}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="575792171"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
<object class="NSColorSpace" key="NSCustomColorSpace">
<int key="NSID">2</int>
</object>
</object>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">view</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="191373211"/>
</object>
<int key="connectionID">3</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">dataSource</string>
<reference key="source" ref="575792171"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">5</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="575792171"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">6</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">_tableView</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="575792171"/>
</object>
<int key="connectionID">7</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<reference key="object" ref="0"/>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1</int>
<reference key="object" ref="191373211"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="575792171"/>
</object>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="372490531"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="975951072"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">4</int>
<reference key="object" ref="575792171"/>
<reference key="parent" ref="191373211"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-1.CustomClassName</string>
<string>-2.CustomClassName</string>
<string>1.IBEditorWindowLastContentRect</string>
<string>1.IBPluginDependency</string>
<string>4.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>IASKSpecifierValuesViewController</string>
<string>UIResponder</string>
<string>{{556, 432}, {320, 460}}</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="sourceID"/>
<int key="maxID">7</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">IASKSpecifierValuesViewController</string>
<string key="superclassName">UIViewController</string>
<object class="NSMutableDictionary" key="outlets">
<string key="NS.key.0">_tableView</string>
<string key="NS.object.0">UITableView</string>
</object>
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
<string key="NS.key.0">_tableView</string>
<object class="IBToOneOutletInfo" key="NS.object.0">
<string key="name">_tableView</string>
<string key="candidateClassName">UITableView</string>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/IASKSpecifierValuesViewController.h</string>
</object>
</object>
</object>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
<integer value="768" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
<integer value="3000" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">294</string>
</data>
</archive>

View File

@@ -0,0 +1,13 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := RakNet
MY_PREFIX := $(LOCAL_PATH)/RakNetSources/
MY_SOURCES := $(wildcard $(MY_PREFIX)*.cpp)
LOCAL_SRC_FILES += $(MY_SOURCES:$(MY_PREFIX)%=RakNetSources/%)
LOCAL_CFLAGS := -Wno-psabi $(LOCAL_CFLAGS)
include $(BUILD_STATIC_LIBRARY)

View File

@@ -0,0 +1,4 @@
APP_PLATFORM := android-8
APP_STL := stlport_static
APP_OPTIM := release
APP_ABI := armeabi-v7a

View File

@@ -0,0 +1,4 @@
FILE(GLOB raknetCPPSources ./RaknetSources/*.cpp)
set(CMAKE_C_FLAGS -m32)
set(CMAKE_CXX_FLAGS -m32)
add_library(raknet ${raknetCPPSources})

View File

@@ -0,0 +1,23 @@
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
/// Usage of RakNet is subject to the appropriate license agreement.
///
#ifndef __AUTOPATCHER_PATCH_CONTEXT_H
#define __AUTOPATCHER_PATCH_CONTEXT_H
enum PatchContext
{
PC_HASH_1_WITH_PATCH,
PC_HASH_2_WITH_PATCH,
PC_WRITE_FILE,
PC_ERROR_FILE_WRITE_FAILURE,
PC_ERROR_PATCH_TARGET_MISSING,
PC_ERROR_PATCH_APPLICATION_FAILURE,
PC_ERROR_PATCH_RESULT_CHECKSUM_FAILURE,
PC_NOTICE_WILL_COPY_ON_RESTART,
PC_NOTICE_FILE_DOWNLOADED,
PC_NOTICE_FILE_DOWNLOADED_PATCH,
};
#endif

View File

@@ -0,0 +1,72 @@
///
/// \file AutopatcherRepositoryInterface.h
/// \brief An interface used by AutopatcherServer to get the data necessary to run an autopatcher.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
/// Usage of RakNet is subject to the appropriate license agreement.
///
#ifndef __AUTOPATCHER_REPOSITORY_INTERFACE_H
#define __AUTOPATCHER_REPOSITORY_INTERFACE_H
#include "IncrementalReadInterface.h"
#include "SimpleMutex.h"
namespace RakNet
{
/// Forward declarations
class FileList;
class BitStream;
/// An interface used by AutopatcherServer to get the data necessary to run an autopatcher. This is up to you to implement for custom repository solutions.
class AutopatcherRepositoryInterface : public IncrementalReadInterface
{
public:
/// Get list of files added and deleted since a certain date. This is used by AutopatcherServer and not usually explicitly called.
/// \param[in] applicationName A null terminated string identifying the application
/// \param[out] addedFiles A list of the current versions of filenames with hashes as their data that were created after \a sinceData
/// \param[out] deletedFiles A list of the current versions of filenames that were deleted after \a sinceData
/// \param[in] An input date, in whatever format your repository uses
/// \param[out] currentDate The current server date, in whatever format your repository uses
/// \return True on success, false on failure.
virtual bool GetChangelistSinceDate(const char *applicationName, FileList *addedFiles, FileList *deletedFiles, double sinceDate)=0;
/// Get patches (or files) for every file in input, assuming that input has a hash for each of those files.
/// \param[in] applicationName A null terminated string identifying the application
/// \param[in] input A list of files with SHA1_LENGTH byte hashes to get from the database.
/// \param[out] patchList You should return list of files with either the filedata or the patch. This is a subset of \a input. The context data for each file will be either PC_WRITE_FILE (to just write the file) or PC_HASH_WITH_PATCH (to patch). If PC_HASH_WITH_PATCH, then the file contains a SHA1_LENGTH byte patch followed by the hash. The datalength is patchlength + SHA1_LENGTH
/// \param[out] currentDate The current server date, in whatever format your repository uses
/// \return True on success, false on failure.
virtual bool GetPatches(const char *applicationName, FileList *input, FileList *patchList)=0;
/// For the most recent update, return files that were patched, added, or deleted. For files that were patched, return both the patch in \a patchedFiles and the current version in \a updatedFiles
/// The cache will be used if the client last patched between \a priorRowPatchTime and \a mostRecentRowPatchTime
/// No files changed will be returned to the client if the client last patched after mostRecentRowPatchTime
/// \param[in,out] applicationName Name of the application to get patches for. If empty, uses the most recently updated application, and the string will be updated to reflect this name.
/// \param[out] patchedFiles Given the most recent update, if a file was patched, add it to this list. The context data for each file will be PC_HASH_WITH_PATCH. The first 4 bytes of data should be a hash of the file being patched. The second 4 bytes should be the hash of the file after the patch. The remaining bytes should be the patch itself.
/// \param[out] updatedFiles The current value of the file. List should have the same length and order as \a patchedFiles
/// \param[out] updatedFileHashes The hash of the current value of the file. List should have the same length and order as \a patchedFiles
/// \param[out] deletedFiles Files that were deleted in the last patch.
/// \param[out] priorRowPatchTime When the patch before the most recent patch took place. 0 means never.
/// \param[out] mostRecentRowPatchTime When the most recent patch took place. 0 means never.
/// \return true on success, false on failure
virtual bool GetMostRecentChangelistWithPatches(
RakNet::RakString &applicationName,
FileList *patchedFiles,
FileList *updatedFiles,
FileList *updatedFileHashes,
FileList *deletedFiles,
double *priorRowPatchTime,
double *mostRecentRowPatchTime)=0;
/// \return Whatever this function returns is sent from the AutopatcherServer to the AutopatcherClient when one of the above functions returns false.
virtual const char *GetLastError(void) const=0;
/// \return Passed to FileListTransfer::Send() as the _chunkSize parameter.
virtual const int GetIncrementalReadChunkSize(void) const=0;
};
} // namespace RakNet
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,332 @@
#include "CCRakNetSlidingWindow.h"
#if USE_SLIDING_WINDOW_CONGESTION_CONTROL==1
static const double UNSET_TIME_US=-1;
#if CC_TIME_TYPE_BYTES==4
static const CCTimeType SYN=10;
#else
static const CCTimeType SYN=10000;
#endif
#include "MTUSize.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "RakAssert.h"
#include "RakAlloca.h"
using namespace RakNet;
// ****************************************************** PUBLIC METHODS ******************************************************
CCRakNetSlidingWindow::CCRakNetSlidingWindow()
{
}
// ----------------------------------------------------------------------------------------------------------------------------
CCRakNetSlidingWindow::~CCRakNetSlidingWindow()
{
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::Init(CCTimeType curTime, uint32_t maxDatagramPayload)
{
(void) curTime;
RTT=UNSET_TIME_US;
MAXIMUM_MTU_INCLUDING_UDP_HEADER=maxDatagramPayload;
cwnd=maxDatagramPayload;
ssThresh=0.0;
oldestUnsentAck=0;
nextDatagramSequenceNumber=0;
nextCongestionControlBlock=0;
backoffThisBlock=speedUpThisBlock=false;
expectedNextSequenceNumber=0;
_isContinuousSend=false;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::Update(CCTimeType curTime, bool hasDataToSendOrResend)
{
(void) curTime;
(void) hasDataToSendOrResend;
}
// ----------------------------------------------------------------------------------------------------------------------------
int CCRakNetSlidingWindow::GetRetransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend)
{
(void) curTime;
(void) isContinuousSend;
(void) timeSinceLastTick;
return unacknowledgedBytes;
}
// ----------------------------------------------------------------------------------------------------------------------------
int CCRakNetSlidingWindow::GetTransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend)
{
(void) curTime;
(void) timeSinceLastTick;
_isContinuousSend=isContinuousSend;
if (unacknowledgedBytes<=cwnd)
return (int) (cwnd-unacknowledgedBytes);
else
return 0;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetSlidingWindow::ShouldSendACKs(CCTimeType curTime, CCTimeType estimatedTimeToNextTick)
{
CCTimeType rto = GetSenderRTOForACK();
(void) estimatedTimeToNextTick;
// iphone crashes on comparison between double and int64 http://www.jenkinssoftware.com/forum/index.php?topic=2717.0
if (rto==(CCTimeType) UNSET_TIME_US)
{
// Unknown how long until the remote system will retransmit, so better send right away
return true;
}
return curTime >= oldestUnsentAck + SYN;
}
// ----------------------------------------------------------------------------------------------------------------------------
DatagramSequenceNumberType CCRakNetSlidingWindow::GetNextDatagramSequenceNumber(void)
{
return nextDatagramSequenceNumber;
}
// ----------------------------------------------------------------------------------------------------------------------------
DatagramSequenceNumberType CCRakNetSlidingWindow::GetAndIncrementNextDatagramSequenceNumber(void)
{
DatagramSequenceNumberType dsnt=nextDatagramSequenceNumber;
nextDatagramSequenceNumber++;
return dsnt;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnSendBytes(CCTimeType curTime, uint32_t numBytes)
{
(void) curTime;
(void) numBytes;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnGotPacketPair(DatagramSequenceNumberType datagramSequenceNumber, uint32_t sizeInBytes, CCTimeType curTime)
{
(void) curTime;
(void) sizeInBytes;
(void) datagramSequenceNumber;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetSlidingWindow::OnGotPacket(DatagramSequenceNumberType datagramSequenceNumber, bool isContinuousSend, CCTimeType curTime, uint32_t sizeInBytes, uint32_t *skippedMessageCount)
{
(void) curTime;
(void) sizeInBytes;
(void) isContinuousSend;
if (oldestUnsentAck==0)
oldestUnsentAck=curTime;
if (datagramSequenceNumber==expectedNextSequenceNumber)
{
*skippedMessageCount=0;
expectedNextSequenceNumber=datagramSequenceNumber+(DatagramSequenceNumberType)1;
}
else if (GreaterThan(datagramSequenceNumber, expectedNextSequenceNumber))
{
*skippedMessageCount=datagramSequenceNumber-expectedNextSequenceNumber;
// Sanity check, just use timeout resend if this was really valid
if (*skippedMessageCount>1000)
{
// During testing, the nat punchthrough server got 51200 on the first packet. I have no idea where this comes from, but has happened twice
if (*skippedMessageCount>(uint32_t)50000)
return false;
*skippedMessageCount=1000;
}
expectedNextSequenceNumber=datagramSequenceNumber+(DatagramSequenceNumberType)1;
}
else
{
*skippedMessageCount=0;
}
return true;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnResend(CCTimeType curTime)
{
(void) curTime;
if (_isContinuousSend && backoffThisBlock==false && cwnd>MAXIMUM_MTU_INCLUDING_UDP_HEADER*2)
{
ssThresh=cwnd/2;
if (ssThresh<MAXIMUM_MTU_INCLUDING_UDP_HEADER)
ssThresh=MAXIMUM_MTU_INCLUDING_UDP_HEADER;
cwnd=MAXIMUM_MTU_INCLUDING_UDP_HEADER;
// Only backoff once per period
backoffThisBlock=true;
}
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnNAK(CCTimeType curTime, DatagramSequenceNumberType nakSequenceNumber)
{
(void) nakSequenceNumber;
OnResend(curTime);
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnAck(CCTimeType curTime, CCTimeType rtt, bool hasBAndAS, BytesPerMicrosecond _B, BytesPerMicrosecond _AS, double totalUserDataBytesAcked, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber )
{
(void) _B;
(void) totalUserDataBytesAcked;
(void) _AS;
(void) hasBAndAS;
(void) curTime;
(void) rtt;
RTT=(double) rtt;
_isContinuousSend=isContinuousSend;
if (isContinuousSend==false)
return;
bool isNewCongestionControlPeriod;
isNewCongestionControlPeriod = GreaterThan(sequenceNumber, nextCongestionControlBlock);
if (isNewCongestionControlPeriod)
{
nextCongestionControlBlock=nextDatagramSequenceNumber;
backoffThisBlock=false;
speedUpThisBlock=false;
}
if (IsInSlowStart())
{
// if (isNewCongestionControlPeriod)
{
// Keep the number in range to avoid overflow
if (cwnd<10000000)
{
cwnd*=2;
if (cwnd>ssThresh && ssThresh!=0)
{
cwnd=ssThresh;
cwnd+=MAXIMUM_MTU_INCLUDING_UDP_HEADER*MAXIMUM_MTU_INCLUDING_UDP_HEADER/cwnd;
}
}
}
}
else
{
if (isNewCongestionControlPeriod)
cwnd+=MAXIMUM_MTU_INCLUDING_UDP_HEADER*MAXIMUM_MTU_INCLUDING_UDP_HEADER/cwnd;
}
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnDuplicateAck( CCTimeType curTime, DatagramSequenceNumberType sequenceNumber )
{
(void) sequenceNumber;
OnResend(curTime);
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnSendAckGetBAndAS(CCTimeType curTime, bool *hasBAndAS, BytesPerMicrosecond *_B, BytesPerMicrosecond *_AS)
{
(void) curTime;
(void) _B;
(void) _AS;
*hasBAndAS=false;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnSendAck(CCTimeType curTime, uint32_t numBytes)
{
(void) curTime;
(void) numBytes;
oldestUnsentAck=0;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnSendNACK(CCTimeType curTime, uint32_t numBytes)
{
(void) curTime;
(void) numBytes;
}
// ----------------------------------------------------------------------------------------------------------------------------
CCTimeType CCRakNetSlidingWindow::GetRTOForRetransmission(void) const
{
#if CC_TIME_TYPE_BYTES==4
const CCTimeType maxThreshold=2000;
const CCTimeType minThreshold=100;
#else
const CCTimeType maxThreshold=2000000;
const CCTimeType minThreshold=100000;
#endif
if (RTT==UNSET_TIME_US)
{
return maxThreshold;
}
if (RTT * 3 > maxThreshold)
return maxThreshold;
if (RTT * 3 < minThreshold)
return minThreshold;
return (CCTimeType) RTT * 3;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::SetMTU(uint32_t bytes)
{
MAXIMUM_MTU_INCLUDING_UDP_HEADER=bytes;
}
// ----------------------------------------------------------------------------------------------------------------------------
uint32_t CCRakNetSlidingWindow::GetMTU(void) const
{
return MAXIMUM_MTU_INCLUDING_UDP_HEADER;
}
// ----------------------------------------------------------------------------------------------------------------------------
BytesPerMicrosecond CCRakNetSlidingWindow::GetLocalReceiveRate(CCTimeType currentTime) const
{
(void) currentTime;
return 0; // TODO
}
// ----------------------------------------------------------------------------------------------------------------------------
double CCRakNetSlidingWindow::GetRTT(void) const
{
if (RTT==UNSET_TIME_US)
return 0.0;
return RTT;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetSlidingWindow::GreaterThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b)
{
// a > b?
const DatagramSequenceNumberType halfSpan =(DatagramSequenceNumberType) (((DatagramSequenceNumberType)(const uint32_t)-1)/(DatagramSequenceNumberType)2);
return b!=a && b-a>halfSpan;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetSlidingWindow::LessThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b)
{
// a < b?
const DatagramSequenceNumberType halfSpan = ((DatagramSequenceNumberType)(const uint32_t)-1)/(DatagramSequenceNumberType)2;
return b!=a && b-a<halfSpan;
}
// ----------------------------------------------------------------------------------------------------------------------------
uint64_t CCRakNetSlidingWindow::GetBytesPerSecondLimitByCongestionControl(void) const
{
return 0; // TODO
}
// ----------------------------------------------------------------------------------------------------------------------------
CCTimeType CCRakNetSlidingWindow::GetSenderRTOForACK(void) const
{
if (RTT==UNSET_TIME_US)
return (CCTimeType) UNSET_TIME_US;
return (CCTimeType)(RTT + SYN);
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetSlidingWindow::IsInSlowStart(void) const
{
return cwnd <= ssThresh || ssThresh==0;
}
// ----------------------------------------------------------------------------------------------------------------------------
#endif

View File

@@ -0,0 +1,209 @@
/*
http://www.ssfnet.org/Exchange/tcp/tcpTutorialNotes.html
cwnd=max bytes allowed on wire at once
Start:
cwnd=mtu
ssthresh=unlimited
Slow start:
On ack cwnd*=2
congestion avoidance:
On ack during new period
cwnd+=mtu*mtu/cwnd
on loss or duplicate ack during period:
sshtresh=cwnd/2
cwnd=MTU
This reenters slow start
If cwnd < ssthresh, then use slow start
else use congestion avoidance
*/
#include "RakNetDefines.h"
#if USE_SLIDING_WINDOW_CONGESTION_CONTROL==1
#ifndef __CONGESTION_CONTROL_SLIDING_WINDOW_H
#define __CONGESTION_CONTROL_SLIDING_WINDOW_H
#include "NativeTypes.h"
#include "RakNetTime.h"
#include "RakNetTypes.h"
#include "DS_Queue.h"
/// Sizeof an UDP header in byte
#define UDP_HEADER_SIZE 28
#define CC_DEBUG_PRINTF_1(x)
#define CC_DEBUG_PRINTF_2(x,y)
#define CC_DEBUG_PRINTF_3(x,y,z)
#define CC_DEBUG_PRINTF_4(x,y,z,a)
#define CC_DEBUG_PRINTF_5(x,y,z,a,b)
//#define CC_DEBUG_PRINTF_1(x) printf(x)
//#define CC_DEBUG_PRINTF_2(x,y) printf(x,y)
//#define CC_DEBUG_PRINTF_3(x,y,z) printf(x,y,z)
//#define CC_DEBUG_PRINTF_4(x,y,z,a) printf(x,y,z,a)
//#define CC_DEBUG_PRINTF_5(x,y,z,a,b) printf(x,y,z,a,b)
/// Set to 4 if you are using the iPod Touch TG. See http://www.jenkinssoftware.com/forum/index.php?topic=2717.0
#define CC_TIME_TYPE_BYTES 8
typedef RakNet::TimeUS CCTimeType;
typedef RakNet::uint24_t DatagramSequenceNumberType;
typedef double BytesPerMicrosecond;
typedef double BytesPerSecond;
typedef double MicrosecondsPerByte;
namespace RakNet
{
class CCRakNetSlidingWindow
{
public:
CCRakNetSlidingWindow();
~CCRakNetSlidingWindow();
/// Reset all variables to their initial states, for a new connection
void Init(CCTimeType curTime, uint32_t maxDatagramPayload);
/// Update over time
void Update(CCTimeType curTime, bool hasDataToSendOrResend);
int GetRetransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend);
int GetTransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend);
/// Acks do not have to be sent immediately. Instead, they can be buffered up such that groups of acks are sent at a time
/// This reduces overall bandwidth usage
/// How long they can be buffered depends on the retransmit time of the sender
/// Should call once per update tick, and send if needed
bool ShouldSendACKs(CCTimeType curTime, CCTimeType estimatedTimeToNextTick);
/// Every data packet sent must contain a sequence number
/// Call this function to get it. The sequence number is passed into OnGotPacketPair()
DatagramSequenceNumberType GetAndIncrementNextDatagramSequenceNumber(void);
DatagramSequenceNumberType GetNextDatagramSequenceNumber(void);
/// Call this when you send packets
/// Every 15th and 16th packets should be sent as a packet pair if possible
/// When packets marked as a packet pair arrive, pass to OnGotPacketPair()
/// When any packets arrive, (additionally) pass to OnGotPacket
/// Packets should contain our system time, so we can pass rtt to OnNonDuplicateAck()
void OnSendBytes(CCTimeType curTime, uint32_t numBytes);
/// Call this when you get a packet pair
void OnGotPacketPair(DatagramSequenceNumberType datagramSequenceNumber, uint32_t sizeInBytes, CCTimeType curTime);
/// Call this when you get a packet (including packet pairs)
/// If the DatagramSequenceNumberType is out of order, skippedMessageCount will be non-zero
/// In that case, send a NAK for every sequence number up to that count
bool OnGotPacket(DatagramSequenceNumberType datagramSequenceNumber, bool isContinuousSend, CCTimeType curTime, uint32_t sizeInBytes, uint32_t *skippedMessageCount);
/// Call when you get a NAK, with the sequence number of the lost message
/// Affects the congestion control
void OnResend(CCTimeType curTime);
void OnNAK(CCTimeType curTime, DatagramSequenceNumberType nakSequenceNumber);
/// Call this when an ACK arrives.
/// hasBAndAS are possibly written with the ack, see OnSendAck()
/// B and AS are used in the calculations in UpdateWindowSizeAndAckOnAckPerSyn
/// B and AS are updated at most once per SYN
void OnAck(CCTimeType curTime, CCTimeType rtt, bool hasBAndAS, BytesPerMicrosecond _B, BytesPerMicrosecond _AS, double totalUserDataBytesAcked, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber );
void OnDuplicateAck( CCTimeType curTime, DatagramSequenceNumberType sequenceNumber );
/// Call when you send an ack, to see if the ack should have the B and AS parameters transmitted
/// Call before calling OnSendAck()
void OnSendAckGetBAndAS(CCTimeType curTime, bool *hasBAndAS, BytesPerMicrosecond *_B, BytesPerMicrosecond *_AS);
/// Call when we send an ack, to write B and AS if needed
/// B and AS are only written once per SYN, to prevent slow calculations
/// Also updates SND, the period between sends, since data is written out
/// Be sure to call OnSendAckGetBAndAS() before calling OnSendAck(), since whether you write it or not affects \a numBytes
void OnSendAck(CCTimeType curTime, uint32_t numBytes);
/// Call when we send a NACK
/// Also updates SND, the period between sends, since data is written out
void OnSendNACK(CCTimeType curTime, uint32_t numBytes);
/// Retransmission time out for the sender
/// If the time difference between when a message was last transmitted, and the current time is greater than RTO then packet is eligible for retransmission, pending congestion control
/// RTO = (RTT + 4 * RTTVar) + SYN
/// If we have been continuously sending for the last RTO, and no ACK or NAK at all, SND*=2;
/// This is per message, which is different from UDT, but RakNet supports packetloss with continuing data where UDT is only RELIABLE_ORDERED
/// Minimum value is 100 milliseconds
CCTimeType GetRTOForRetransmission(void) const;
/// Set the maximum amount of data that can be sent in one datagram
/// Default to MAXIMUM_MTU_SIZE-UDP_HEADER_SIZE
void SetMTU(uint32_t bytes);
/// Return what was set by SetMTU()
uint32_t GetMTU(void) const;
/// Query for statistics
BytesPerMicrosecond GetLocalSendRate(void) const {return 0;}
BytesPerMicrosecond GetLocalReceiveRate(CCTimeType currentTime) const;
BytesPerMicrosecond GetRemoveReceiveRate(void) const {return 0;}
//BytesPerMicrosecond GetEstimatedBandwidth(void) const {return B;}
BytesPerMicrosecond GetEstimatedBandwidth(void) const {return GetLinkCapacityBytesPerSecond()*1000000.0;}
double GetLinkCapacityBytesPerSecond(void) const {return 0;}
/// Query for statistics
double GetRTT(void) const;
bool GetIsInSlowStart(void) const {return IsInSlowStart();}
uint32_t GetCWNDLimit(void) const {return (uint32_t) 0;}
/// Is a > b, accounting for variable overflow?
static bool GreaterThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b);
/// Is a < b, accounting for variable overflow?
static bool LessThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b);
// void SetTimeBetweenSendsLimit(unsigned int bitsPerSecond);
uint64_t GetBytesPerSecondLimitByCongestionControl(void) const;
protected:
// Maximum amount of bytes that the user can send, e.g. the size of one full datagram
uint32_t MAXIMUM_MTU_INCLUDING_UDP_HEADER;
double RTT;
double cwnd; // max bytes on wire
double ssThresh; // Threshhold between slow start and congestion avoidance
/// When we get an ack, if oldestUnsentAck==0, set it to the current time
/// When we send out acks, set oldestUnsentAck to 0
CCTimeType oldestUnsentAck;
CCTimeType GetSenderRTOForACK(void) const;
/// Every outgoing datagram is assigned a sequence number, which increments by 1 every assignment
DatagramSequenceNumberType nextDatagramSequenceNumber;
DatagramSequenceNumberType nextCongestionControlBlock;
bool backoffThisBlock, speedUpThisBlock;
/// Track which datagram sequence numbers have arrived.
/// If a sequence number is skipped, send a NAK for all skipped messages
DatagramSequenceNumberType expectedNextSequenceNumber;
bool _isContinuousSend;
bool IsInSlowStart(void) const;
};
}
#endif
#endif

View File

@@ -0,0 +1,798 @@
#include "CCRakNetUDT.h"
#if USE_SLIDING_WINDOW_CONGESTION_CONTROL!=1
#include "Rand.h"
#include "MTUSize.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
//#include <memory.h>
#include "RakAssert.h"
#include "RakAlloca.h"
using namespace RakNet;
static const double UNSET_TIME_US=-1;
static const double CWND_MIN_THRESHOLD=2.0;
static const double UNDEFINED_TRANSFER_RATE=0.0;
/// Interval at which to update aspects of the system
/// 1. send acks
/// 2. update time interval between outgoing packets
/// 3, Yodate retransmit timeout
#if CC_TIME_TYPE_BYTES==4
static const CCTimeType SYN=10;
#else
static const CCTimeType SYN=10000;
#endif
#if CC_TIME_TYPE_BYTES==4
#define MAX_RTT 1000
#define RTT_TOLERANCE 30
#else
#define MAX_RTT 1000000
#define RTT_TOLERANCE 30000
#endif
double RTTVarMultiple=4.0;
// ****************************************************** PUBLIC METHODS ******************************************************
CCRakNetUDT::CCRakNetUDT()
{
}
// ----------------------------------------------------------------------------------------------------------------------------
CCRakNetUDT::~CCRakNetUDT()
{
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::Init(CCTimeType curTime, uint32_t maxDatagramPayload)
{
(void) curTime;
nextSYNUpdate=0;
packetPairRecieptHistoryWriteIndex=0;
packetArrivalHistoryWriteIndex=0;
packetArrivalHistoryWriteCount=0;
RTT=UNSET_TIME_US;
// RTTVar=UNSET_TIME_US;
isInSlowStart=true;
NAKCount=1000;
AvgNAKNum=1;
DecInterval=1;
DecCount=0;
nextDatagramSequenceNumber=0;
lastPacketPairPacketArrivalTime=0;
lastPacketPairSequenceNumber=(DatagramSequenceNumberType)(const uint32_t)-1;
lastPacketArrivalTime=0;
CWND=CWND_MIN_THRESHOLD;
lastUpdateWindowSizeAndAck=0;
lastTransmitOfBAndAS=0;
ExpCount=1.0;
totalUserDataBytesSent=0;
oldestUnsentAck=0;
MAXIMUM_MTU_INCLUDING_UDP_HEADER=maxDatagramPayload;
CWND_MAX_THRESHOLD=RESEND_BUFFER_ARRAY_LENGTH;
#if CC_TIME_TYPE_BYTES==4
const BytesPerMicrosecond DEFAULT_TRANSFER_RATE=(BytesPerMicrosecond) 3.6;
#else
const BytesPerMicrosecond DEFAULT_TRANSFER_RATE=(BytesPerMicrosecond) .0036;
#endif
#if CC_TIME_TYPE_BYTES==4
lastRttOnIncreaseSendRate=1000;
#else
lastRttOnIncreaseSendRate=1000000;
#endif
nextCongestionControlBlock=0;
lastRtt=0;
// B=DEFAULT_TRANSFER_RATE;
AS=UNDEFINED_TRANSFER_RATE;
const MicrosecondsPerByte DEFAULT_BYTE_INTERVAL=(MicrosecondsPerByte) (1.0/DEFAULT_TRANSFER_RATE);
SND=DEFAULT_BYTE_INTERVAL;
expectedNextSequenceNumber=0;
sendBAndASCount=0;
packetArrivalHistoryContinuousGapsIndex=0;
//packetPairRecipetHistoryGapsIndex=0;
hasWrittenToPacketPairReceiptHistory=false;
InitPacketArrivalHistory();
estimatedLinkCapacityBytesPerSecond=0;
bytesCanSendThisTick=0;
hadPacketlossThisBlock=false;
pingsLastInterval.Clear(__FILE__,__LINE__);
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::SetMTU(uint32_t bytes)
{
MAXIMUM_MTU_INCLUDING_UDP_HEADER=bytes;
}
// ----------------------------------------------------------------------------------------------------------------------------
uint32_t CCRakNetUDT::GetMTU(void) const
{
return MAXIMUM_MTU_INCLUDING_UDP_HEADER;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::Update(CCTimeType curTime, bool hasDataToSendOrResend)
{
(void) hasDataToSendOrResend;
(void) curTime;
return;
// I suspect this is causing major lag
/*
if (hasDataToSendOrResend==false)
halveSNDOnNoDataTime=0;
else if (halveSNDOnNoDataTime==0)
{
UpdateHalveSNDOnNoDataTime(curTime);
ExpCount=1.0;
}
// If you send, and get no data at all from that time to RTO, then halve send rate7
if (HasHalveSNDOnNoDataTimeElapsed(curTime))
{
/// 2000 bytes per second
/// 0.0005 seconds per byte
/// 0.5 milliseconds per byte
/// 500 microseconds per byte
// printf("No incoming data, halving send rate\n");
SND*=2.0;
CapMinSnd(_FILE_AND_LINE_);
ExpCount+=1.0;
if (ExpCount>8.0)
ExpCount=8.0;
UpdateHalveSNDOnNoDataTime(curTime);
}
*/
}
// ----------------------------------------------------------------------------------------------------------------------------
int CCRakNetUDT::GetRetransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend)
{
(void) curTime;
if (isInSlowStart)
{
uint32_t CWNDLimit = (uint32_t) (CWND*MAXIMUM_MTU_INCLUDING_UDP_HEADER);
return CWNDLimit;
}
return GetTransmissionBandwidth(curTime,timeSinceLastTick,unacknowledgedBytes,isContinuousSend);
}
// ----------------------------------------------------------------------------------------------------------------------------
int CCRakNetUDT::GetTransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend)
{
(void) curTime;
if (isInSlowStart)
{
uint32_t CWNDLimit = (uint32_t) (CWND*MAXIMUM_MTU_INCLUDING_UDP_HEADER-unacknowledgedBytes);
return CWNDLimit;
}
if (bytesCanSendThisTick>0)
bytesCanSendThisTick=0;
#if CC_TIME_TYPE_BYTES==4
if (isContinuousSend==false && timeSinceLastTick>100)
timeSinceLastTick=100;
#else
if (isContinuousSend==false && timeSinceLastTick>100000)
timeSinceLastTick=100000;
#endif
bytesCanSendThisTick=(int)((double)timeSinceLastTick*((double)1.0/SND)+(double)bytesCanSendThisTick);
if (bytesCanSendThisTick>0)
return bytesCanSendThisTick;
return 0;
}
uint64_t CCRakNetUDT::GetBytesPerSecondLimitByCongestionControl(void) const
{
if (isInSlowStart)
return 0;
#if CC_TIME_TYPE_BYTES==4
return (uint64_t) ((double)1.0/(SND*1000.0));
#else
return (uint64_t) ((double)1.0/(SND*1000000.0));
#endif
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetUDT::ShouldSendACKs(CCTimeType curTime, CCTimeType estimatedTimeToNextTick)
{
CCTimeType rto = GetSenderRTOForACK();
// iphone crashes on comparison between double and int64 http://www.jenkinssoftware.com/forum/index.php?topic=2717.0
if (rto==(CCTimeType) UNSET_TIME_US)
{
// Unknown how long until the remote system will retransmit, so better send right away
return true;
}
// CCTimeType remoteRetransmitTime=oldestUnsentAck+rto-RTT*.5;
// CCTimeType ackArrivalTimeIfWeDelay=RTT*.5+estimatedTimeToNextTick+curTime;
// return ackArrivalTimeIfWeDelay<remoteRetransmitTime;
// Simplified equation
// GU: At least one ACK should be sent per SYN, otherwise your protocol will increase slower.
return curTime >= oldestUnsentAck + SYN ||
estimatedTimeToNextTick+curTime < oldestUnsentAck+rto-RTT;
}
// ----------------------------------------------------------------------------------------------------------------------------
DatagramSequenceNumberType CCRakNetUDT::GetNextDatagramSequenceNumber(void)
{
return nextDatagramSequenceNumber;
}
// ----------------------------------------------------------------------------------------------------------------------------
DatagramSequenceNumberType CCRakNetUDT::GetAndIncrementNextDatagramSequenceNumber(void)
{
DatagramSequenceNumberType dsnt=nextDatagramSequenceNumber;
nextDatagramSequenceNumber++;
return dsnt;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnSendBytes(CCTimeType curTime, uint32_t numBytes)
{
(void) curTime;
totalUserDataBytesSent+=numBytes;
if (isInSlowStart==false)
bytesCanSendThisTick-=numBytes;
}
// ****************************************************** PROTECTED METHODS ******************************************************
void CCRakNetUDT::SetNextSYNUpdate(CCTimeType currentTime)
{
nextSYNUpdate+=SYN;
if (nextSYNUpdate < currentTime)
nextSYNUpdate=currentTime+SYN;
}
// ----------------------------------------------------------------------------------------------------------------------------
BytesPerMicrosecond CCRakNetUDT::ReceiverCalculateDataArrivalRate(CCTimeType curTime) const
{
(void) curTime;
// Not an instantaneous measurement
/*
if (continuousBytesReceivedStartTime!=0 && curTime>continuousBytesReceivedStartTime)
{
#if CC_TIME_TYPE_BYTES==4
const CCTimeType threshold=100;
#else
const CCTimeType threshold=100000;
#endif
if (curTime-continuousBytesReceivedStartTime>threshold)
return (BytesPerMicrosecond) continuousBytesReceived/(BytesPerMicrosecond) (curTime-continuousBytesReceivedStartTime);
}
return UNDEFINED_TRANSFER_RATE;
*/
if (packetArrivalHistoryWriteCount<CC_RAKNET_UDT_PACKET_HISTORY_LENGTH)
return UNDEFINED_TRANSFER_RATE;
BytesPerMicrosecond median = ReceiverCalculateDataArrivalRateMedian();
int i;
const BytesPerMicrosecond oneEighthMedian=median*(1.0/8.0);
const BytesPerMicrosecond eightTimesMedian=median*8.0f;
BytesPerMicrosecond medianListLength=0.0;
BytesPerMicrosecond sum=0.0;
// Find average of acceptedMedianValues
for (i=0; i < CC_RAKNET_UDT_PACKET_HISTORY_LENGTH; i++)
{
if (packetArrivalHistory[i]>=oneEighthMedian &&
packetArrivalHistory[i]<eightTimesMedian)
{
medianListLength=medianListLength+1.0;
sum+=packetArrivalHistory[i];
}
}
if (medianListLength==0.0)
return UNDEFINED_TRANSFER_RATE;
return sum/medianListLength;
}
// ----------------------------------------------------------------------------------------------------------------------------
BytesPerMicrosecond CCRakNetUDT::ReceiverCalculateDataArrivalRateMedian(void) const
{
return CalculateListMedianRecursive(packetArrivalHistory, CC_RAKNET_UDT_PACKET_HISTORY_LENGTH, 0, 0);
}
// ----------------------------------------------------------------------------------------------------------------------------
BytesPerMicrosecond CCRakNetUDT::CalculateListMedianRecursive(const BytesPerMicrosecond inputList[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH], int inputListLength, int lessThanSum, int greaterThanSum)
{
BytesPerMicrosecond lessThanMedian[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH], greaterThanMedian[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH];
int lessThanMedianListLength=0, greaterThanMedianListLength=0;
BytesPerMicrosecond median=inputList[0];
int i;
for (i=1; i < inputListLength; i++)
{
// If same value, spread among lists evenly
if (inputList[i]<median || ((i&1)==0 && inputList[i]==median))
lessThanMedian[lessThanMedianListLength++]=inputList[i];
else
greaterThanMedian[greaterThanMedianListLength++]=inputList[i];
}
RakAssert(CC_RAKNET_UDT_PACKET_HISTORY_LENGTH%2==0);
if (lessThanMedianListLength+lessThanSum==greaterThanMedianListLength+greaterThanSum+1 ||
lessThanMedianListLength+lessThanSum==greaterThanMedianListLength+greaterThanSum-1)
return median;
if (lessThanMedianListLength+lessThanSum < greaterThanMedianListLength+greaterThanSum)
{
lessThanMedian[lessThanMedianListLength++]=median;
return CalculateListMedianRecursive(greaterThanMedian, greaterThanMedianListLength, lessThanMedianListLength+lessThanSum, greaterThanSum);
}
else
{
greaterThanMedian[greaterThanMedianListLength++]=median;
return CalculateListMedianRecursive(lessThanMedian, lessThanMedianListLength, lessThanSum, greaterThanMedianListLength+greaterThanSum);
}
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetUDT::GreaterThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b)
{
// a > b?
const DatagramSequenceNumberType halfSpan =(DatagramSequenceNumberType) (((DatagramSequenceNumberType)(const uint32_t)-1)/(DatagramSequenceNumberType)2);
return b!=a && b-a>halfSpan;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetUDT::LessThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b)
{
// a < b?
const DatagramSequenceNumberType halfSpan = ((DatagramSequenceNumberType)(const uint32_t)-1)/(DatagramSequenceNumberType)2;
return b!=a && b-a<halfSpan;
}
// ----------------------------------------------------------------------------------------------------------------------------
CCTimeType CCRakNetUDT::GetSenderRTOForACK(void) const
{
if (RTT==UNSET_TIME_US)
return (CCTimeType) UNSET_TIME_US;
double RTTVar = maxRTT-minRTT;
return (CCTimeType)(RTT + RTTVarMultiple * RTTVar + SYN);
}
// ----------------------------------------------------------------------------------------------------------------------------
CCTimeType CCRakNetUDT::GetRTOForRetransmission(void) const
{
#if CC_TIME_TYPE_BYTES==4
const CCTimeType maxThreshold=10000;
const CCTimeType minThreshold=100;
#else
const CCTimeType maxThreshold=1000000;
const CCTimeType minThreshold=100000;
#endif
if (RTT==UNSET_TIME_US)
{
return (CCTimeType) maxThreshold;
}
CCTimeType ret = lastRttOnIncreaseSendRate*2;
if (ret<minThreshold)
return minThreshold;
if (ret>maxThreshold)
return maxThreshold;
return ret;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnResend(CCTimeType curTime)
{
(void) curTime;
if (isInSlowStart)
{
if (AS!=UNDEFINED_TRANSFER_RATE)
EndSlowStart();
return;
}
if (hadPacketlossThisBlock==false)
{
// Logging
// printf("Sending SLOWER due to Resend, Rate=%f MBPS. Rtt=%i\n", GetLocalSendRate(), lastRtt );
IncreaseTimeBetweenSends();
hadPacketlossThisBlock=true;
}
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnNAK(CCTimeType curTime, DatagramSequenceNumberType nakSequenceNumber)
{
(void) nakSequenceNumber;
(void) curTime;
if (isInSlowStart)
{
if (AS!=UNDEFINED_TRANSFER_RATE)
EndSlowStart();
return;
}
if (hadPacketlossThisBlock==false)
{
// Logging
//printf("Sending SLOWER due to NAK, Rate=%f MBPS. Rtt=%i\n", GetLocalSendRate(), lastRtt );
if (pingsLastInterval.Size()>10)
{
for (int i=0; i < 10; i++)
printf("%i, ", pingsLastInterval[pingsLastInterval.Size()-1-i]/1000);
}
printf("\n");
IncreaseTimeBetweenSends();
hadPacketlossThisBlock=true;
}
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::EndSlowStart(void)
{
RakAssert(isInSlowStart==true);
RakAssert(AS!=UNDEFINED_TRANSFER_RATE);
// This overestimates
estimatedLinkCapacityBytesPerSecond=AS * 1000000.0;
isInSlowStart=false;
SND=1.0/AS;
CapMinSnd(_FILE_AND_LINE_);
// printf("ENDING SLOW START\n");
#if CC_TIME_TYPE_BYTES==4
// printf("Initial SND=%f Kilobytes per second\n", 1.0/SND);
#else
// printf("Initial SND=%f Megabytes per second\n", 1.0/SND);
#endif
if (SND > .1)
PrintLowBandwidthWarning();
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnGotPacketPair(DatagramSequenceNumberType datagramSequenceNumber, uint32_t sizeInBytes, CCTimeType curTime)
{
(void) datagramSequenceNumber;
(void) sizeInBytes;
(void) curTime;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetUDT::OnGotPacket(DatagramSequenceNumberType datagramSequenceNumber, bool isContinuousSend, CCTimeType curTime, uint32_t sizeInBytes, uint32_t *skippedMessageCount)
{
CC_DEBUG_PRINTF_2("R%i ",datagramSequenceNumber.val);
if (datagramSequenceNumber==expectedNextSequenceNumber)
{
*skippedMessageCount=0;
expectedNextSequenceNumber=datagramSequenceNumber+(DatagramSequenceNumberType)1;
}
else if (GreaterThan(datagramSequenceNumber, expectedNextSequenceNumber))
{
*skippedMessageCount=datagramSequenceNumber-expectedNextSequenceNumber;
// Sanity check, just use timeout resend if this was really valid
if (*skippedMessageCount>1000)
{
// During testing, the nat punchthrough server got 51200 on the first packet. I have no idea where this comes from, but has happened twice
if (*skippedMessageCount>(uint32_t)50000)
return false;
*skippedMessageCount=1000;
}
expectedNextSequenceNumber=datagramSequenceNumber+(DatagramSequenceNumberType)1;
}
else
{
*skippedMessageCount=0;
}
if (curTime>lastPacketArrivalTime)
{
CCTimeType interval = curTime-lastPacketArrivalTime;
// printf("Packet arrival gap is %I64u\n", (interval));
if (isContinuousSend)
{
continuousBytesReceived+=sizeInBytes;
if (continuousBytesReceivedStartTime==0)
continuousBytesReceivedStartTime=lastPacketArrivalTime;
mostRecentPacketArrivalHistory=(BytesPerMicrosecond)sizeInBytes/(BytesPerMicrosecond)interval;
// if (mostRecentPacketArrivalHistory < (BytesPerMicrosecond)0.0035)
// {
// printf("%s:%i LIKELY BUG: Calculated packetArrivalHistory is below 28.8 Kbps modem\nReport to rakkar@jenkinssoftware.com with file and line number\n", _FILE_AND_LINE_);
// }
packetArrivalHistoryContinuousGaps[packetArrivalHistoryContinuousGapsIndex++]=(int) interval;
packetArrivalHistoryContinuousGapsIndex&=(CC_RAKNET_UDT_PACKET_HISTORY_LENGTH-1);
packetArrivalHistoryWriteCount++;
packetArrivalHistory[packetArrivalHistoryWriteIndex++]=mostRecentPacketArrivalHistory;
// Wrap to 0 at the end of the range
// Assumes power of 2 for CC_RAKNET_UDT_PACKET_HISTORY_LENGTH
packetArrivalHistoryWriteIndex&=(CC_RAKNET_UDT_PACKET_HISTORY_LENGTH-1);
}
else
{
continuousBytesReceivedStartTime=0;
continuousBytesReceived=0;
}
lastPacketArrivalTime=curTime;
}
return true;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnAck(CCTimeType curTime, CCTimeType rtt, bool hasBAndAS, BytesPerMicrosecond _B, BytesPerMicrosecond _AS, double totalUserDataBytesAcked, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber )
{
#if CC_TIME_TYPE_BYTES==4
RakAssert(rtt < 10000);
#else
RakAssert(rtt < 10000000);
#endif
(void) _B;
if (hasBAndAS)
{
/// RakAssert(_B!=UNDEFINED_TRANSFER_RATE && _AS!=UNDEFINED_TRANSFER_RATE);
// B=B * .875 + _B * .125;
// AS is packet arrival rate
RakAssert(_AS!=UNDEFINED_TRANSFER_RATE);
AS=_AS;
CC_DEBUG_PRINTF_4("ArrivalRate=%f linkCap=%f incomingLinkCap=%f\n", _AS,B,_B);
}
if (oldestUnsentAck==0)
oldestUnsentAck=curTime;
if (isInSlowStart==true)
{
nextCongestionControlBlock=nextDatagramSequenceNumber;
lastRttOnIncreaseSendRate=rtt;
UpdateWindowSizeAndAckOnAckPreSlowStart(totalUserDataBytesAcked);
}
else
{
UpdateWindowSizeAndAckOnAckPerSyn(curTime, rtt, isContinuousSend, sequenceNumber);
}
lastUpdateWindowSizeAndAck=curTime;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnSendAckGetBAndAS(CCTimeType curTime, bool *hasBAndAS, BytesPerMicrosecond *_B, BytesPerMicrosecond *_AS)
{
if (curTime>lastTransmitOfBAndAS+SYN)
{
*_B=0;
*_AS=ReceiverCalculateDataArrivalRate(curTime);
if (*_AS==UNDEFINED_TRANSFER_RATE)
{
*hasBAndAS=false;
}
else
{
*hasBAndAS=true;
}
}
else
{
*hasBAndAS=false;
}
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnSendAck(CCTimeType curTime, uint32_t numBytes)
{
(void) numBytes;
(void) curTime;
// This is not accounted for on the remote system, and thus causes bandwidth to be underutilized
//UpdateNextAllowedSend(curTime, numBytes+UDP_HEADER_SIZE);
oldestUnsentAck=0;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnSendNACK(CCTimeType curTime, uint32_t numBytes)
{
(void) numBytes;
(void) curTime;
// This is not accounted for on the remote system, and thus causes bandwidth to be underutilized
// UpdateNextAllowedSend(curTime, numBytes+UDP_HEADER_SIZE);
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::UpdateWindowSizeAndAckOnAckPreSlowStart(double totalUserDataBytesAcked)
{
// During slow start, max window size is the number of full packets that have been sent out
// CWND=(double) ((double)totalUserDataBytesSent/(double)MAXIMUM_MTU_INCLUDING_UDP_HEADER);
CC_DEBUG_PRINTF_3("CWND increasing from %f to %f\n", CWND, (double) ((double)totalUserDataBytesAcked/(double)MAXIMUM_MTU_INCLUDING_UDP_HEADER));
CWND=(double) ((double)totalUserDataBytesAcked/(double)MAXIMUM_MTU_INCLUDING_UDP_HEADER);
if (CWND>=CWND_MAX_THRESHOLD)
{
CWND=CWND_MAX_THRESHOLD;
if (AS!=UNDEFINED_TRANSFER_RATE)
EndSlowStart();
}
if (CWND<CWND_MIN_THRESHOLD)
CWND=CWND_MIN_THRESHOLD;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::UpdateWindowSizeAndAckOnAckPerSyn(CCTimeType curTime, CCTimeType rtt, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber)
{
(void) curTime;
(void) sequenceNumber;
if (isContinuousSend==false)
{
nextCongestionControlBlock=nextDatagramSequenceNumber;
pingsLastInterval.Clear(__FILE__,__LINE__);
return;
}
pingsLastInterval.Push(rtt,__FILE__,__LINE__);
static const int intervalSize=33; // Should be odd
if (pingsLastInterval.Size()>intervalSize)
pingsLastInterval.Pop();
if (GreaterThan(sequenceNumber, nextCongestionControlBlock) &&
sequenceNumber-nextCongestionControlBlock>=intervalSize &&
pingsLastInterval.Size()==intervalSize)
{
double slopeSum=0.0;
double average=(double) pingsLastInterval[0];
int sampleSize=pingsLastInterval.Size();
for (int i=1; i < sampleSize; i++)
{
slopeSum+=(double)pingsLastInterval[i]-(double)pingsLastInterval[i-1];
average+=pingsLastInterval[i];
}
average/=sampleSize;
if (hadPacketlossThisBlock==true)
{
}
else if (slopeSum < -.10*average)
{
// Logging
//printf("Ping dropping. slope=%f%%. Rate=%f MBPS. Rtt=%i\n", 100.0*slopeSum/average, GetLocalSendRate(), rtt );
}
else if (slopeSum > .10*average)
{
// Logging
//printf("Ping rising. slope=%f%%. Rate=%f MBPS. Rtt=%i\n", 100.0*slopeSum/average, GetLocalSendRate(), rtt );
IncreaseTimeBetweenSends();
}
else
{
// Logging
//printf("Ping stable. slope=%f%%. Rate=%f MBPS. Rtt=%i\n", 100.0*slopeSum/average, GetLocalSendRate(), rtt );
// No packetloss over time threshhold, and rtt decreased, so send faster
lastRttOnIncreaseSendRate=rtt;
DecreaseTimeBetweenSends();
}
pingsLastInterval.Clear(__FILE__,__LINE__);
hadPacketlossThisBlock=false;
nextCongestionControlBlock=nextDatagramSequenceNumber;
}
lastRtt=rtt;
}
// ----------------------------------------------------------------------------------------------------------------------------
double CCRakNetUDT::BytesPerMicrosecondToPacketsPerMillisecond(BytesPerMicrosecond in)
{
#if CC_TIME_TYPE_BYTES==4
const BytesPerMicrosecond factor = 1.0 / (BytesPerMicrosecond) MAXIMUM_MTU_INCLUDING_UDP_HEADER;
#else
const BytesPerMicrosecond factor = 1000.0 / (BytesPerMicrosecond) MAXIMUM_MTU_INCLUDING_UDP_HEADER;
#endif
return in * factor;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::InitPacketArrivalHistory(void)
{
unsigned int i;
for (i=0; i < CC_RAKNET_UDT_PACKET_HISTORY_LENGTH; i++)
{
packetArrivalHistory[i]=UNDEFINED_TRANSFER_RATE;
packetArrivalHistoryContinuousGaps[i]=0;
}
packetArrivalHistoryWriteCount=0;
continuousBytesReceived=0;
continuousBytesReceivedStartTime=0;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::PrintLowBandwidthWarning(void)
{
/*
printf("\n-------LOW BANDWIDTH -----\n");
if (isInSlowStart==false)
printf("SND=%f Megabytes per second\n", 1.0/SND);
printf("Window size=%f\n", CWND);
printf("Pipe from packet pair = %f megabytes per second\n", B);
printf("RTT=%f milliseconds\n", RTT/1000.0);
printf("RTT Variance=%f milliseconds\n", RTTVar/1000.0);
printf("Retransmission=%i milliseconds\n", GetRTOForRetransmission()/1000);
printf("Packet arrival rate on the remote system=%f megabytes per second\n", AS);
printf("Packet arrival rate on our system=%f megabytes per second\n", ReceiverCalculateDataArrivalRate());
printf("isInSlowStart=%i\n", isInSlowStart);
printf("---------------\n");
*/
}
BytesPerMicrosecond CCRakNetUDT::GetLocalReceiveRate(CCTimeType currentTime) const
{
return ReceiverCalculateDataArrivalRate(currentTime);
}
double CCRakNetUDT::GetRTT(void) const
{
if (RTT==UNSET_TIME_US)
return 0.0;
return RTT;
}
void CCRakNetUDT::CapMinSnd(const char *file, int line)
{
(void) file;
(void) line;
if (SND > 500)
{
SND=500;
CC_DEBUG_PRINTF_3("%s:%i LIKELY BUG: SND has gotten above 500 microseconds between messages (28.8 modem)\nReport to rakkar@jenkinssoftware.com with file and line number\n", file, line);
}
}
void CCRakNetUDT::IncreaseTimeBetweenSends(void)
{
// In order to converge, bigger numbers have to increase slower and decrease faster
// SND==500 then increment is .02
// SND==0 then increment is near 0
// (SND+1.0) brings it to the range of 1 to 501
// Square the number, which is the range of 1 to 251001
// Divide by 251001, which is the range of 1/251001 to 1
double increment;
increment = .02 * ((SND+1.0) * (SND+1.0)) / (501.0*501.0) ;
// SND=500 then increment=.02
// SND=0 then increment=near 0
SND*=(1.02 - increment);
// SND=0 then fast increase, slow decrease
// SND=500 then slow increase, fast decrease
CapMinSnd(__FILE__,__LINE__);
}
void CCRakNetUDT::DecreaseTimeBetweenSends(void)
{
double increment;
increment = .01 * ((SND+1.0) * (SND+1.0)) / (501.0*501.0) ;
// SND=500 then increment=.01
// SND=0 then increment=near 0
SND*=(.99 - increment);
}
/*
void CCRakNetUDT::SetTimeBetweenSendsLimit(unsigned int bitsPerSecond)
{
// bitsPerSecond / 1000000 = bitsPerMicrosecond
// bitsPerMicrosecond / 8 = BytesPerMicrosecond
// 1 / BytesPerMicrosecond = MicrosecondsPerByte
// 1 / ( (bitsPerSecond / 1000000) / 8 ) =
// 1 / (bitsPerSecond / 8000000) =
// 8000000 / bitsPerSecond
#if CC_TIME_TYPE_BYTES==4
MicrosecondsPerByte limit = (MicrosecondsPerByte) 8000 / (MicrosecondsPerByte)bitsPerSecond;
#else
MicrosecondsPerByte limit = (MicrosecondsPerByte) 8000000 / (MicrosecondsPerByte)bitsPerSecond;
#endif
if (limit > SND)
SND=limit;
}
*/
#endif

View File

@@ -0,0 +1,394 @@
#include "RakNetDefines.h"
#if USE_SLIDING_WINDOW_CONGESTION_CONTROL!=1
#ifndef __CONGESTION_CONTROL_UDT_H
#define __CONGESTION_CONTROL_UDT_H
#include "NativeTypes.h"
#include "RakNetTime.h"
#include "RakNetTypes.h"
#include "DS_Queue.h"
/// Set to 4 if you are using the iPod Touch TG. See http://www.jenkinssoftware.com/forum/index.php?topic=2717.0
#define CC_TIME_TYPE_BYTES 8
namespace RakNet
{
typedef uint64_t CCTimeType;
typedef uint24_t DatagramSequenceNumberType;
typedef double BytesPerMicrosecond;
typedef double BytesPerSecond;
typedef double MicrosecondsPerByte;
/// CC_RAKNET_UDT_PACKET_HISTORY_LENGTH should be a power of 2 for the writeIndex variables to wrap properly
#define CC_RAKNET_UDT_PACKET_HISTORY_LENGTH 64
#define RTT_HISTORY_LENGTH 64
/// Sizeof an UDP header in byte
#define UDP_HEADER_SIZE 28
#define CC_DEBUG_PRINTF_1(x)
#define CC_DEBUG_PRINTF_2(x,y)
#define CC_DEBUG_PRINTF_3(x,y,z)
#define CC_DEBUG_PRINTF_4(x,y,z,a)
#define CC_DEBUG_PRINTF_5(x,y,z,a,b)
//#define CC_DEBUG_PRINTF_1(x) printf(x)
//#define CC_DEBUG_PRINTF_2(x,y) printf(x,y)
//#define CC_DEBUG_PRINTF_3(x,y,z) printf(x,y,z)
//#define CC_DEBUG_PRINTF_4(x,y,z,a) printf(x,y,z,a)
//#define CC_DEBUG_PRINTF_5(x,y,z,a,b) printf(x,y,z,a,b)
/// \brief Encapsulates UDT congestion control, as used by RakNet
/// Requirements:
/// <OL>
/// <LI>Each datagram is no more than MAXIMUM_MTU_SIZE, after accounting for the UDP header
/// <LI>Each datagram containing a user message has a sequence number which is set after calling OnSendBytes(). Set it by calling GetAndIncrementNextDatagramSequenceNumber()
/// <LI>System is designed to be used from a single thread.
/// <LI>Each packet should have a timeout time based on GetSenderRTOForACK(). If this time elapses, add the packet to the head of the send list for retransmission.
/// </OL>
///
/// Recommended:
/// <OL>
/// <LI>Call sendto in its own thread. This takes a significant amount of time in high speed networks.
/// </OL>
///
/// Algorithm:
/// <OL>
/// <LI>On a new connection, call Init()
/// <LI>On a periodic interval (SYN time is the best) call Update(). Also call ShouldSendACKs(), and send buffered ACKS if it returns true.
/// <LI>Call OnSendAck() when sending acks.
/// <LI>When you want to send or resend data, call GetNumberOfBytesToSend(). It will return you enough bytes to keep you busy for \a estimatedTimeToNextTick. You can send more than this to fill out a datagram, or to send packet pairs
/// <LI>Call OnSendBytes() when sending datagrams.
/// <LI>When data arrives, record the sequence number and buffer an ACK for it, to be sent from Update() if ShouldSendACKs() returns true
/// <LI>Every 16 packets that you send, send two of them back to back (a packet pair) as long as both packets are the same size. If you don't have two packets the same size, it is fine to defer this until you do.
/// <LI>When you get a packet, call OnGotPacket(). If the packet is also either of a packet pair, call OnGotPacketPair()
/// <LI>If you get a packet, and the sequence number is not 1 + the last sequence number, send a NAK. On the remote system, call OnNAK() and resend that message.
/// <LI>If you get an ACK, remove that message from retransmission. Call OnNonDuplicateAck().
/// <LI>If a message is not ACKed for GetRTOForRetransmission(), resend it.
/// </OL>
class CCRakNetUDT
{
public:
CCRakNetUDT();
~CCRakNetUDT();
/// Reset all variables to their initial states, for a new connection
void Init(CCTimeType curTime, uint32_t maxDatagramPayload);
/// Update over time
void Update(CCTimeType curTime, bool hasDataToSendOrResend);
int GetRetransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend);
int GetTransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend);
/// Acks do not have to be sent immediately. Instead, they can be buffered up such that groups of acks are sent at a time
/// This reduces overall bandwidth usage
/// How long they can be buffered depends on the retransmit time of the sender
/// Should call once per update tick, and send if needed
bool ShouldSendACKs(CCTimeType curTime, CCTimeType estimatedTimeToNextTick);
/// Every data packet sent must contain a sequence number
/// Call this function to get it. The sequence number is passed into OnGotPacketPair()
DatagramSequenceNumberType GetAndIncrementNextDatagramSequenceNumber(void);
DatagramSequenceNumberType GetNextDatagramSequenceNumber(void);
/// Call this when you send packets
/// Every 15th and 16th packets should be sent as a packet pair if possible
/// When packets marked as a packet pair arrive, pass to OnGotPacketPair()
/// When any packets arrive, (additionally) pass to OnGotPacket
/// Packets should contain our system time, so we can pass rtt to OnNonDuplicateAck()
void OnSendBytes(CCTimeType curTime, uint32_t numBytes);
/// Call this when you get a packet pair
void OnGotPacketPair(DatagramSequenceNumberType datagramSequenceNumber, uint32_t sizeInBytes, CCTimeType curTime);
/// Call this when you get a packet (including packet pairs)
/// If the DatagramSequenceNumberType is out of order, skippedMessageCount will be non-zero
/// In that case, send a NAK for every sequence number up to that count
bool OnGotPacket(DatagramSequenceNumberType datagramSequenceNumber, bool isContinuousSend, CCTimeType curTime, uint32_t sizeInBytes, uint32_t *skippedMessageCount);
/// Call when you get a NAK, with the sequence number of the lost message
/// Affects the congestion control
void OnResend(CCTimeType curTime);
void OnNAK(CCTimeType curTime, DatagramSequenceNumberType nakSequenceNumber);
/// Call this when an ACK arrives.
/// hasBAndAS are possibly written with the ack, see OnSendAck()
/// B and AS are used in the calculations in UpdateWindowSizeAndAckOnAckPerSyn
/// B and AS are updated at most once per SYN
void OnAck(CCTimeType curTime, CCTimeType rtt, bool hasBAndAS, BytesPerMicrosecond _B, BytesPerMicrosecond _AS, double totalUserDataBytesAcked, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber );
void OnDuplicateAck( CCTimeType curTime, DatagramSequenceNumberType sequenceNumber ) {}
/// Call when you send an ack, to see if the ack should have the B and AS parameters transmitted
/// Call before calling OnSendAck()
void OnSendAckGetBAndAS(CCTimeType curTime, bool *hasBAndAS, BytesPerMicrosecond *_B, BytesPerMicrosecond *_AS);
/// Call when we send an ack, to write B and AS if needed
/// B and AS are only written once per SYN, to prevent slow calculations
/// Also updates SND, the period between sends, since data is written out
/// Be sure to call OnSendAckGetBAndAS() before calling OnSendAck(), since whether you write it or not affects \a numBytes
void OnSendAck(CCTimeType curTime, uint32_t numBytes);
/// Call when we send a NACK
/// Also updates SND, the period between sends, since data is written out
void OnSendNACK(CCTimeType curTime, uint32_t numBytes);
/// Retransmission time out for the sender
/// If the time difference between when a message was last transmitted, and the current time is greater than RTO then packet is eligible for retransmission, pending congestion control
/// RTO = (RTT + 4 * RTTVar) + SYN
/// If we have been continuously sending for the last RTO, and no ACK or NAK at all, SND*=2;
/// This is per message, which is different from UDT, but RakNet supports packetloss with continuing data where UDT is only RELIABLE_ORDERED
/// Minimum value is 100 milliseconds
CCTimeType GetRTOForRetransmission(void) const;
/// Set the maximum amount of data that can be sent in one datagram
/// Default to MAXIMUM_MTU_SIZE-UDP_HEADER_SIZE
void SetMTU(uint32_t bytes);
/// Return what was set by SetMTU()
uint32_t GetMTU(void) const;
/// Query for statistics
BytesPerMicrosecond GetLocalSendRate(void) const {return 1.0 / SND;}
BytesPerMicrosecond GetLocalReceiveRate(CCTimeType currentTime) const;
BytesPerMicrosecond GetRemoveReceiveRate(void) const {return AS;}
//BytesPerMicrosecond GetEstimatedBandwidth(void) const {return B;}
BytesPerMicrosecond GetEstimatedBandwidth(void) const {return GetLinkCapacityBytesPerSecond()*1000000.0;}
double GetLinkCapacityBytesPerSecond(void) const {return estimatedLinkCapacityBytesPerSecond;};
/// Query for statistics
double GetRTT(void) const;
bool GetIsInSlowStart(void) const {return isInSlowStart;}
uint32_t GetCWNDLimit(void) const {return (uint32_t) (CWND*MAXIMUM_MTU_INCLUDING_UDP_HEADER);}
/// Is a > b, accounting for variable overflow?
static bool GreaterThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b);
/// Is a < b, accounting for variable overflow?
static bool LessThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b);
// void SetTimeBetweenSendsLimit(unsigned int bitsPerSecond);
uint64_t GetBytesPerSecondLimitByCongestionControl(void) const;
protected:
// --------------------------- PROTECTED VARIABLES ---------------------------
/// time interval between bytes, in microseconds.
/// Only used when slowStart==false
/// Increased over time as we continually get messages
/// Decreased on NAK and timeout
/// Starts at 0 (invalid)
MicrosecondsPerByte SND;
/// Supportive window mechanism, controlling the maximum number of in-flight packets
/// Used both during and after slow-start, but primarily during slow-start
/// Starts at 2, which is also the low threshhold
/// Max is the socket receive buffer / MTU
/// CWND = AS * (RTT + SYN) + 16
double CWND;
/// When we do an update process on the SYN interval, nextSYNUpdate is set to the next time we should update
/// Normally this is nextSYNUpdate+=SYN, in order to update on a consistent schedule
/// However, if this would result in an immediate update yet again, it is set to SYN microseconds past the current time (in case the thread did not update for a long time)
CCTimeType nextSYNUpdate;
/// Index into packetPairRecieptHistory where we will next write
/// The history is always full (starting with default values) so no read index is needed
int packetPairRecieptHistoryWriteIndex;
/// Sent to the sender by the receiver from packetPairRecieptHistory whenever a back to back packet arrives on the receiver
/// Updated by B = B * .875 + incomingB * .125
//BytesPerMicrosecond B;
/// Running round trip time (ping*2)
/// Only sender needs to know this
/// Initialized to UNSET
/// Set to rtt on first calculation
/// Updated gradually by RTT = RTT * 0.875 + rtt * 0.125
double RTT;
/// Round trip time variance
/// Only sender needs to know this
/// Initialized to UNSET
/// Set to rtt on first calculation
// double RTTVar;
/// Update: Use min/max, RTTVar follows current variance too closely resulting in packetloss
double minRTT, maxRTT;
/// Used to calculate packet arrival rate (in UDT) but data arrival rate (in RakNet, where not all datagrams are the same size)
/// Filter is used to cull lowest half of values for bytesPerMicrosecond, to discount spikes and inactivity
/// Referred to in the documentation as AS, data arrival rate
/// AS is sent to the sender and calculated every 10th ack
/// Each node represents (curTime-lastPacketArrivalTime)/bytes
/// Used with ReceiverCalculateDataArrivalRate();
BytesPerMicrosecond packetArrivalHistory[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH];
BytesPerMicrosecond packetArrivalHistoryContinuousGaps[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH];
unsigned char packetArrivalHistoryContinuousGapsIndex;
uint64_t continuousBytesReceived;
CCTimeType continuousBytesReceivedStartTime;
unsigned int packetArrivalHistoryWriteCount;
/// Index into packetArrivalHistory where we will next write
/// The history is always full (starting with default values) so no read index is needed
int packetArrivalHistoryWriteIndex;
/// Tracks the time the last packet that arrived, so BytesPerMicrosecond can be calculated for packetArrivalHistory when a new packet arrives
CCTimeType lastPacketArrivalTime;
/// Data arrival rate from the sender to the receiver, as told to us by the receiver
/// Used to calculate initial sending rate when slow start stops
BytesPerMicrosecond AS;
/// When the receiver last calculated and send B and AS, from packetArrivalHistory and packetPairRecieptHistory
/// Used to prevent it from being calculated and send too frequently, as they are slow operations
CCTimeType lastTransmitOfBAndAS;
/// New connections start in slow start
/// During slow start, SND is not used, only CWND
/// Slow start ends when we get a NAK, or the maximum size of CWND is reached
/// SND is initialized to the inverse of the receiver's packet arrival rate when slow start ends
bool isInSlowStart;
/// How many NAKs arrived this congestion period
/// Initialized to 1 when the congestion period starts
uint32_t NAKCount;
/// How many NAKs do you get on average during a congestion period?
/// Starts at 1
/// Used to generate a random number, DecRandom, between 1 and AvgNAKNum
uint32_t AvgNAKNum;
/// How many times we have decremented SND this congestion period. Used to limit the number of decrements to 5
uint32_t DecCount;
/// Every DecInterval NAKs per congestion period, we decrease the send rate
uint32_t DecInterval;
/// Every outgoing datagram is assigned a sequence number, which increments by 1 every assignment
DatagramSequenceNumberType nextDatagramSequenceNumber;
/// If a packet is marked as a packet pair, lastPacketPairPacketArrivalTime is set to the time it arrives
/// This is used so when the 2nd packet of the pair arrives, we can calculate the time interval between the two
CCTimeType lastPacketPairPacketArrivalTime;
/// If a packet is marked as a packet pair, lastPacketPairSequenceNumber is checked to see if the last packet we got
/// was the packet immediately before the one that arrived
/// If so, we can use lastPacketPairPacketArrivalTime to get the time between the two packets, and thus estimate the link capacity
/// Initialized to -1, so the first packet of a packet pair won't be treated as the second
DatagramSequenceNumberType lastPacketPairSequenceNumber;
/// Used to cap UpdateWindowSizeAndAckOnAckPerSyn() to once speed increase per SYN
/// This is to prevent speeding up faster than congestion control can compensate for
CCTimeType lastUpdateWindowSizeAndAck;
/// Every time SND is halved due to timeout, the RTO is increased
/// This is to prevent massive retransmissions to an unresponsive system
/// Reset on any data arriving
double ExpCount;
/// Total number of user data bytes sent
/// Used to adjust the window size, on ACK, during slow start
uint64_t totalUserDataBytesSent;
/// When we get an ack, if oldestUnsentAck==0, set it to the current time
/// When we send out acks, set oldestUnsentAck to 0
CCTimeType oldestUnsentAck;
// Maximum amount of bytes that the user can send, e.g. the size of one full datagram
uint32_t MAXIMUM_MTU_INCLUDING_UDP_HEADER;
// Max window size
double CWND_MAX_THRESHOLD;
/// Track which datagram sequence numbers have arrived.
/// If a sequence number is skipped, send a NAK for all skipped messages
DatagramSequenceNumberType expectedNextSequenceNumber;
// How many times have we sent B and AS? Used to force it to send at least CC_RAKNET_UDT_PACKET_HISTORY_LENGTH times
// Otherwise, the default values in the array generate inaccuracy
uint32_t sendBAndASCount;
/// Most recent values read into the corresponding lists
/// Used during the beginning of a connection, when the median filter is still inaccurate
BytesPerMicrosecond mostRecentPacketArrivalHistory;
bool hasWrittenToPacketPairReceiptHistory;
// uint32_t rttHistory[RTT_HISTORY_LENGTH];
// uint32_t rttHistoryIndex;
// uint32_t rttHistoryWriteCount;
// uint32_t rttSum, rttLow;
// CCTimeType lastSndUpdateTime;
double estimatedLinkCapacityBytesPerSecond;
// --------------------------- PROTECTED METHODS ---------------------------
/// Update nextSYNUpdate by SYN, or the same amount past the current time if no updates have occurred for a long time
void SetNextSYNUpdate(CCTimeType currentTime);
/// Returns the rate of data arrival, based on packets arriving on the sender.
BytesPerMicrosecond ReceiverCalculateDataArrivalRate(CCTimeType curTime) const;
/// Returns the median of the data arrival rate
BytesPerMicrosecond ReceiverCalculateDataArrivalRateMedian(void) const;
/// Calculates the median an array of BytesPerMicrosecond
static BytesPerMicrosecond CalculateListMedianRecursive(const BytesPerMicrosecond inputList[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH], int inputListLength, int lessThanSum, int greaterThanSum);
// static uint32_t CalculateListMedianRecursive(const uint32_t inputList[RTT_HISTORY_LENGTH], int inputListLength, int lessThanSum, int greaterThanSum);
/// Same as GetRTOForRetransmission, but does not factor in ExpCount
/// This is because the receiver does not know ExpCount for the sender, and even if it did, acks shouldn't be delayed for this reason
CCTimeType GetSenderRTOForACK(void) const;
/// Stop slow start, and enter normal transfer rate
void EndSlowStart(void);
/// Does the named conversion
inline double BytesPerMicrosecondToPacketsPerMillisecond(BytesPerMicrosecond in);
/// Update the round trip time, from ACK or ACK2
//void UpdateRTT(CCTimeType rtt);
/// Update the corresponding variables pre-slow start
void UpdateWindowSizeAndAckOnAckPreSlowStart(double totalUserDataBytesAcked);
/// Update the corresponding variables post-slow start
void UpdateWindowSizeAndAckOnAckPerSyn(CCTimeType curTime, CCTimeType rtt, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber);
/// Sets halveSNDOnNoDataTime to the future, and also resets ExpCount, which is used to multiple the RTO on no data arriving at all
void ResetOnDataArrivalHalveSNDOnNoDataTime(CCTimeType curTime);
// Init array
void InitPacketArrivalHistory(void);
// Printf
void PrintLowBandwidthWarning(void);
// Bug: SND can sometimes get super high - have seen 11693
void CapMinSnd(const char *file, int line);
void DecreaseTimeBetweenSends(void);
void IncreaseTimeBetweenSends(void);
int bytesCanSendThisTick;
CCTimeType lastRttOnIncreaseSendRate;
CCTimeType lastRtt;
DatagramSequenceNumberType nextCongestionControlBlock;
bool hadPacketlossThisBlock;
DataStructures::Queue<CCTimeType> pingsLastInterval;
};
}
#endif
#endif

View File

@@ -0,0 +1,97 @@
/**
* @file
* @brief CheckSum implementation from http://www.flounder.com/checksum.htm
*
*/
#include "CheckSum.h"
/****************************************************************************
* CheckSum::add
* Inputs:
* unsigned int d: word to add
* Result: void
*
* Effect:
* Adds the bytes of the unsigned int to the CheckSum
****************************************************************************/
void CheckSum::Add ( unsigned int value )
{
union
{
unsigned int value;
unsigned char bytes[ 4 ];
}
data;
data.value = value;
for ( unsigned int i = 0; i < sizeof( data.bytes ); i++ )
Add ( data.bytes[ i ] )
;
} // CheckSum::add(unsigned int)
/****************************************************************************
* CheckSum::add
* Inputs:
* unsigned short value:
* Result: void
*
* Effect:
* Adds the bytes of the unsigned short value to the CheckSum
****************************************************************************/
void CheckSum::Add ( unsigned short value )
{
union
{
unsigned short value;
unsigned char bytes[ 2 ];
}
data;
data.value = value;
for ( unsigned int i = 0; i < sizeof( data.bytes ); i++ )
Add ( data.bytes[ i ] )
;
} // CheckSum::add(unsigned short)
/****************************************************************************
* CheckSum::add
* Inputs:
* unsigned char value:
* Result: void
*
* Effect:
* Adds the byte to the CheckSum
****************************************************************************/
void CheckSum::Add ( unsigned char value )
{
unsigned char cipher = (unsigned char)( value ^ ( r >> 8 ) );
r = ( cipher + r ) * c1 + c2;
sum += cipher;
} // CheckSum::add(unsigned char)
/****************************************************************************
* CheckSum::add
* Inputs:
* LPunsigned char b: pointer to byte array
* unsigned int length: count
* Result: void
*
* Effect:
* Adds the bytes to the CheckSum
****************************************************************************/
void CheckSum::Add ( unsigned char *b, unsigned int length )
{
for ( unsigned int i = 0; i < length; i++ )
Add ( b[ i ] )
;
} // CheckSum::add(LPunsigned char, unsigned int)

View File

@@ -0,0 +1,53 @@
///
/// \file CheckSum.cpp
/// \brief [Internal] CheckSum implementation from http://www.flounder.com/checksum.htm
///
#ifndef __CHECKSUM_H
#define __CHECKSUM_H
#include "RakMemoryOverride.h"
/// Generates and validates checksums
class CheckSum
{
public:
/// Default constructor
CheckSum()
{
Clear();
}
void Clear()
{
sum = 0;
r = 55665;
c1 = 52845;
c2 = 22719;
}
void Add ( unsigned int w );
void Add ( unsigned short w );
void Add ( unsigned char* b, unsigned int length );
void Add ( unsigned char b );
unsigned int Get ()
{
return sum;
}
protected:
unsigned short r;
unsigned short c1;
unsigned short c2;
unsigned int sum;
};
#endif

View File

@@ -0,0 +1,242 @@
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_CloudClient==1
#include "CloudClient.h"
#include "GetTime.h"
#include "MessageIdentifiers.h"
#include "BitStream.h"
#include "RakPeerInterface.h"
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(CloudClient,CloudClient);
CloudClient::CloudClient()
{
callback=0;
allocator=&unsetDefaultAllocator;
}
CloudClient::~CloudClient()
{
}
void CloudClient::SetDefaultCallbacks(CloudAllocator *_allocator, CloudClientCallback *_callback)
{
callback=_callback;
allocator=_allocator;
}
void CloudClient::Post(CloudKey *cloudKey, const unsigned char *data, uint32_t dataLengthBytes, RakNetGUID systemIdentifier)
{
RakAssert(cloudKey);
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_POST_REQUEST);
cloudKey->Serialize(true,&bsOut);
if (data==0)
dataLengthBytes=0;
bsOut.Write(dataLengthBytes);
if (dataLengthBytes>0)
bsOut.WriteAlignedBytes((const unsigned char*) data, dataLengthBytes);
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
}
void CloudClient::Release(DataStructures::List<CloudKey> &keys, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_RELEASE_REQUEST);
RakAssert(keys.Size() < (uint16_t)-1 );
bsOut.WriteCasted<uint16_t>(keys.Size());
for (uint16_t i=0; i < keys.Size(); i++)
{
keys[i].Serialize(true,&bsOut);
}
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
}
bool CloudClient::Get(CloudQuery *keyQuery, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_GET_REQUEST);
keyQuery->Serialize(true, &bsOut);
bsOut.WriteCasted<uint16_t>(0); // Specific systems
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
return true;
}
bool CloudClient::Get(CloudQuery *keyQuery, DataStructures::List<RakNetGUID> &specificSystems, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_GET_REQUEST);
keyQuery->Serialize(true, &bsOut);
bsOut.WriteCasted<uint16_t>(specificSystems.Size());
RakAssert(specificSystems.Size() < (uint16_t)-1 );
for (uint16_t i=0; i < specificSystems.Size(); i++)
{
bsOut.Write(specificSystems[i]);
}
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
return true;
}
bool CloudClient::Get(CloudQuery *keyQuery, DataStructures::List<CloudQueryRow*> &specificSystems, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_GET_REQUEST);
keyQuery->Serialize(true, &bsOut);
bsOut.WriteCasted<uint16_t>(specificSystems.Size());
RakAssert(specificSystems.Size() < (uint16_t)-1 );
for (uint16_t i=0; i < specificSystems.Size(); i++)
{
if (specificSystems[i]->clientGUID!=UNASSIGNED_RAKNET_GUID)
{
bsOut.Write(true);
bsOut.Write(specificSystems[i]->clientGUID);
}
else
{
bsOut.Write(false);
bsOut.Write(specificSystems[i]->clientSystemAddress);
}
}
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
return true;
}
void CloudClient::Unsubscribe(DataStructures::List<CloudKey> &keys, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_UNSUBSCRIBE_REQUEST);
RakAssert(keys.Size() < (uint16_t)-1 );
bsOut.WriteCasted<uint16_t>(keys.Size());
for (uint16_t i=0; i < keys.Size(); i++)
{
keys[i].Serialize(true,&bsOut);
}
bsOut.WriteCasted<uint16_t>(0);
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
}
void CloudClient::Unsubscribe(DataStructures::List<CloudKey> &keys, DataStructures::List<RakNetGUID> &specificSystems, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_UNSUBSCRIBE_REQUEST);
RakAssert(keys.Size() < (uint16_t)-1 );
bsOut.WriteCasted<uint16_t>(keys.Size());
for (uint16_t i=0; i < keys.Size(); i++)
{
keys[i].Serialize(true,&bsOut);
}
bsOut.WriteCasted<uint16_t>(specificSystems.Size());
RakAssert(specificSystems.Size() < (uint16_t)-1 );
for (uint16_t i=0; i < specificSystems.Size(); i++)
{
bsOut.Write(specificSystems[i]);
}
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
}
void CloudClient::Unsubscribe(DataStructures::List<CloudKey> &keys, DataStructures::List<CloudQueryRow*> &specificSystems, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_UNSUBSCRIBE_REQUEST);
RakAssert(keys.Size() < (uint16_t)-1 );
bsOut.WriteCasted<uint16_t>(keys.Size());
for (uint16_t i=0; i < keys.Size(); i++)
{
keys[i].Serialize(true,&bsOut);
}
bsOut.WriteCasted<uint16_t>(specificSystems.Size());
RakAssert(specificSystems.Size() < (uint16_t)-1 );
for (uint16_t i=0; i < specificSystems.Size(); i++)
{
if (specificSystems[i]->clientGUID!=UNASSIGNED_RAKNET_GUID)
{
bsOut.Write(true);
bsOut.Write(specificSystems[i]->clientGUID);
}
else
{
bsOut.Write(false);
bsOut.Write(specificSystems[i]->clientSystemAddress);
}
}
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
}
PluginReceiveResult CloudClient::OnReceive(Packet *packet)
{
(void) packet;
return RR_CONTINUE_PROCESSING;
}
void CloudClient::OnGetReponse(Packet *packet, CloudClientCallback *_callback, CloudAllocator *_allocator)
{
if (_callback==0)
_callback=callback;
if (_allocator==0)
_allocator=allocator;
CloudQueryResult cloudQueryResult;
RakNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(sizeof(MessageID));
cloudQueryResult.Serialize(false,&bsIn,_allocator);
bool deallocateRowsAfterReturn=true;
_callback->OnGet(&cloudQueryResult, &deallocateRowsAfterReturn);
if (deallocateRowsAfterReturn)
{
unsigned int i;
for (i=0; i < cloudQueryResult.rowsReturned.Size(); i++)
{
_allocator->DeallocateRowData(cloudQueryResult.rowsReturned[i]->data);
_allocator->DeallocateCloudQueryRow(cloudQueryResult.rowsReturned[i]);
}
}
}
void CloudClient::OnGetReponse(CloudQueryResult *cloudQueryResult, Packet *packet, CloudAllocator *_allocator)
{
if (_allocator==0)
_allocator=allocator;
RakNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(sizeof(MessageID));
cloudQueryResult->Serialize(false,&bsIn,_allocator);
}
void CloudClient::OnSubscriptionNotification(Packet *packet, CloudClientCallback *_callback, CloudAllocator *_allocator)
{
if (_callback==0)
_callback=callback;
if (_allocator==0)
_allocator=allocator;
bool wasUpdated=false;
CloudQueryRow row;
RakNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(sizeof(MessageID));
bsIn.Read(wasUpdated);
row.Serialize(false,&bsIn,_allocator);
bool deallocateRowAfterReturn=true;
_callback->OnSubscriptionNotification(&row, wasUpdated, &deallocateRowAfterReturn);
if (deallocateRowAfterReturn)
{
_allocator->DeallocateRowData(row.data);
}
}
void CloudClient::OnSubscriptionNotification(bool *wasUpdated, CloudQueryRow *row, Packet *packet, CloudAllocator *_allocator)
{
if (_allocator==0)
_allocator=allocator;
RakNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(sizeof(MessageID));
bool b=false;
bsIn.Read(b);
*wasUpdated=b;
row->Serialize(false,&bsIn,_allocator);
}
void CloudClient::DeallocateWithDefaultAllocator(CloudQueryResult *cloudQueryResult)
{
unsigned int i;
for (i=0; i < cloudQueryResult->rowsReturned.Size(); i++)
{
allocator->DeallocateRowData(cloudQueryResult->rowsReturned[i]->data);
allocator->DeallocateCloudQueryRow(cloudQueryResult->rowsReturned[i]);
}
}
void CloudClient::DeallocateWithDefaultAllocator(CloudQueryRow *row)
{
allocator->DeallocateRowData(row->data);
}
#endif

View File

@@ -0,0 +1,163 @@
/// \file CloudClient.h
/// \brief Queries CloudMemoryServer to download data that other clients have uploaded
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_CloudClient==1
#ifndef __CLOUD_CLIENT_H
#define __CLOUD_CLIENT_H
#include "PluginInterface2.h"
#include "CloudCommon.h"
#include "RakMemoryOverride.h"
#include "DS_Hash.h"
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
class CloudClientCallback;
/// \defgroup CLOUD_GROUP CloudComputing
/// \brief Contains the CloudClient and CloudServer plugins
/// \details The CloudServer plugins operates on requests from the CloudClient plugin. The servers are in a fully connected mesh topology, which the clients are connected to any server. Clients can interact with each other by posting and subscribing to memory updates, without being directly connected or even knowing about each other.
/// \ingroup PLUGINS_GROUP
/// \brief Performs Post() and Get() operations on CloudMemoryServer
/// \details A CloudClient is a computer connected to one or more servers in a cloud configuration. Operations by one CloudClient can be received and subscribed to by other instances of CloudClient, without those clients being connected, even on different servers.
/// \ingroup CLOUD_GROUP
class RAK_DLL_EXPORT CloudClient : public PluginInterface2
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(CloudClient)
CloudClient();
virtual ~CloudClient();
/// \brief Set the default callbacks for OnGetReponse(), OnSubscriptionNotification(), and OnSubscriptionDataDeleted()
/// \details Pointers to CloudAllocator and CloudClientCallback can be stored by the system if desired. If a callback is not provided to OnGetReponse(), OnSubscriptionNotification(), OnSubscriptionDataDeleted(), the callback passed here will be used instead.
/// \param[in] _allocator An instance of CloudAllocator
/// \param[in] _callback An instance of CloudClientCallback
virtual void SetDefaultCallbacks(CloudAllocator *_allocator, CloudClientCallback *_callback);
/// \brief Uploads data to the cloud
/// \details Data uploaded to the cloud will be stored by the server sent to, identified by \a systemIdentifier.
/// As long as you are connected to this server, the data will persist. Queries for that data by the Get() operation will
/// return the RakNetGUID and SystemAddress of the uploader, as well as the data itself.
/// Furthermore, if any clients are subscribed to the particular CloudKey passed, those clients will get update notices that the data has changed
/// Passing data with the same \a cloudKey more than once will overwrite the prior value.
/// This call will silently fail if CloudServer::SetMaxUploadBytesPerClient() is exceeded
/// \param[in] cloudKey Identifies the data being uploaded
/// \param[in] data A pointer to data to upload. This pointer does not need to persist past the call
/// \param[in] dataLengthBytes The length in bytes of \a data
/// \param[in] systemIdentifier A remote system running CloudServer that we are already connected to.
virtual void Post(CloudKey *cloudKey, const unsigned char *data, uint32_t dataLengthBytes, RakNetGUID systemIdentifier);
/// \brief Releases one or more data previously uploaded with Post()
/// \details If a remote system has subscribed to one or more of the \a keys uploaded, they will get ID_CLOUD_SUBSCRIPTION_NOTIFICATION notifications containing the last value uploaded before deletions
/// \param[in] cloudKey Identifies the data to release. It is possible to remove uploads from multiple Post() calls at once.
/// \param[in] systemIdentifier A remote system running CloudServer that we are already connected to.
virtual void Release(DataStructures::List<CloudKey> &keys, RakNetGUID systemIdentifier);
/// \brief Gets data from the cloud
/// \details For a given query containing one or more keys, return data that matches those keys.
/// The values will be returned in the ID_CLOUD_GET_RESPONSE packet, which should be passed to OnGetReponse() and will invoke CloudClientCallback::OnGet()
/// CloudQuery::startingRowIndex is used to skip the first n values that would normally be returned..
/// CloudQuery::maxRowsToReturn is used to limit the number of rows returned. The number of rows returned may also be limited by CloudServer::SetMaxBytesPerDownload();
/// CloudQuery::subscribeToResults if set to true, will cause ID_CLOUD_SUBSCRIPTION_NOTIFICATION to be returned to us when any of the keys in the query are updated or are deleted.
/// ID_CLOUD_GET_RESPONSE will be returned even if subscribing to the result list. Only later updates will return ID_CLOUD_SUBSCRIPTION_NOTIFICATION.
/// Calling Get() with CloudQuery::subscribeToResults false, when you are already subscribed, does not remove the subscription. Use Unsubscribe() for this.
/// Resubscribing using the same CloudKey but a different or no \a specificSystems overwrites the subscribed systems for those keys.
/// \param[in] cloudQuery One or more keys, and optional parameters to perform with the Get
/// \param[in] systemIdentifier A remote system running CloudServer that we are already connected to.
/// \param[in] specificSystems It is possible to get or subscribe to updates only for specific uploading CloudClient instances. Pass the desired instances here. The overload that does not have the specificSystems parameter is treated as subscribing to all updates from all clients.
virtual bool Get(CloudQuery *cloudQuery, RakNetGUID systemIdentifier);
virtual bool Get(CloudQuery *cloudQuery, DataStructures::List<RakNetGUID> &specificSystems, RakNetGUID systemIdentifier);
virtual bool Get(CloudQuery *cloudQuery, DataStructures::List<CloudQueryRow*> &specificSystems, RakNetGUID systemIdentifier);
/// \brief Unsubscribe from updates previously subscribed to using Get() with the CloudQuery::subscribeToResults set to true
/// The \a keys and \a specificSystems parameters are logically treated as AND when checking subscriptions on the server
/// The overload that does not take specificSystems unsubscribes to all passed keys, regardless of system
/// You cannot unsubscribe specific systems when previously subscribed to updates from any system. To do this, first Unsubscribe() from all systems, and call Get() with the \a specificSystems parameter explicilty listing the systems you want to subscribe to.
virtual void Unsubscribe(DataStructures::List<CloudKey> &keys, RakNetGUID systemIdentifier);
virtual void Unsubscribe(DataStructures::List<CloudKey> &keys, DataStructures::List<RakNetGUID> &specificSystems, RakNetGUID systemIdentifier);
virtual void Unsubscribe(DataStructures::List<CloudKey> &keys, DataStructures::List<CloudQueryRow*> &specificSystems, RakNetGUID systemIdentifier);
/// \brief Call this when you get ID_CLOUD_GET_RESPONSE
/// If \a callback or \a allocator are 0, the default callbacks passed to SetDefaultCallbacks() are used
/// \param[in] packet Packet structure returned from RakPeerInterface
/// \param[in] _callback Callback to be called from the function containing output parameters. If 0, default is used.
/// \param[in] _allocator Allocator to be used to allocate data. If 0, default is used.
virtual void OnGetReponse(Packet *packet, CloudClientCallback *_callback=0, CloudAllocator *_allocator=0);
/// \brief Call this when you get ID_CLOUD_GET_RESPONSE
/// Different form of OnGetReponse that returns to a structure that you pass, instead of using a callback
/// You are responsible for deallocation with this form
/// If \a allocator is 0, the default callback passed to SetDefaultCallbacks() are used
/// \param[out] cloudQueryResult A pointer to a structure that will be filled out with data
/// \param[in] packet Packet structure returned from RakPeerInterface
/// \param[in] _allocator Allocator to be used to allocate data. If 0, default is used.
virtual void OnGetReponse(CloudQueryResult *cloudQueryResult, Packet *packet, CloudAllocator *_allocator=0);
/// \brief Call this when you get ID_CLOUD_SUBSCRIPTION_NOTIFICATION
/// If \a callback or \a allocator are 0, the default callbacks passed to SetDefaultCallbacks() are used
/// \param[in] packet Packet structure returned from RakPeerInterface
/// \param[in] _callback Callback to be called from the function containing output parameters. If 0, default is used.
/// \param[in] _allocator Allocator to be used to allocate data. If 0, default is used.
virtual void OnSubscriptionNotification(Packet *packet, CloudClientCallback *_callback=0, CloudAllocator *_allocator=0);
/// \brief Call this when you get ID_CLOUD_SUBSCRIPTION_NOTIFICATION
/// Different form of OnSubscriptionNotification that returns to a structure that you pass, instead of using a callback
/// You are responsible for deallocation with this form
/// If \a allocator is 0, the default callback passed to SetDefaultCallbacks() are used
/// \param[out] wasUpdated If true, the row was updated. If false, it was deleted. \a result will contain the last value just before deletion
/// \param[out] row A pointer to a structure that will be filled out with data
/// \param[in] packet Packet structure returned from RakPeerInterface
/// \param[in] _allocator Allocator to be used to allocate data. If 0, default is used.
virtual void OnSubscriptionNotification(bool *wasUpdated, CloudQueryRow *row, Packet *packet, CloudAllocator *_allocator=0);
/// If you never specified an allocator, and used the non-callback form of OnGetReponse(), deallocate cloudQueryResult with this function
virtual void DeallocateWithDefaultAllocator(CloudQueryResult *cloudQueryResult);
/// If you never specified an allocator, and used the non-callback form of OnSubscriptionNotification(), deallocate row with this function
virtual void DeallocateWithDefaultAllocator(CloudQueryRow *row);
protected:
PluginReceiveResult OnReceive(Packet *packet);
CloudClientCallback *callback;
CloudAllocator *allocator;
CloudAllocator unsetDefaultAllocator;
};
/// \ingroup CLOUD_GROUP
/// Parses ID_CLOUD_GET_RESPONSE and ID_CLOUD_SUBSCRIPTION_NOTIFICATION in a convenient callback form
class RAK_DLL_EXPORT CloudClientCallback
{
public:
CloudClientCallback() {}
virtual ~CloudClientCallback() {}
/// \brief Called in response to ID_CLOUD_GET_RESPONSE
/// \param[out] result Contains the original query passed to Get(), and a list of rows returned.
/// \param[out] deallocateRowsAfterReturn CloudQueryResult::rowsReturned will be deallocated after the function returns by default. Set to false to not deallocate these pointers. The pointers are allocated through CloudAllocator.
virtual void OnGet(RakNet::CloudQueryResult *result, bool *deallocateRowsAfterReturn) {(void) result; (void) deallocateRowsAfterReturn;}
/// \brief Called in response to ID_CLOUD_SUBSCRIPTION_NOTIFICATION
/// \param[out] result Contains the row updated
/// \param[out] wasUpdated If true, the row was updated. If false, it was deleted. \a result will contain the last value just before deletion
/// \param[out] deallocateRowAfterReturn \a result will be deallocated after the function returns by default. Set to false to not deallocate these pointers. The pointers are allocated through CloudAllocator.
virtual void OnSubscriptionNotification(RakNet::CloudQueryRow *result, bool wasUpdated, bool *deallocateRowAfterReturn) {(void) result; (void) wasUpdated; (void) deallocateRowAfterReturn;}
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,159 @@
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_CloudClient==1 || _RAKNET_SUPPORT_CloudServer==1
#include "CloudCommon.h"
#include "BitStream.h"
using namespace RakNet;
int RakNet::CloudKeyComp(const CloudKey &key, const CloudKey &data)
{
if (key.primaryKey < data.primaryKey)
return -1;
if (key.primaryKey > data.primaryKey)
return 1;
if (key.secondaryKey < data.secondaryKey)
return -1;
if (key.secondaryKey > data.secondaryKey)
return 1;
return 0;
}
CloudQueryRow* CloudAllocator::AllocateCloudQueryRow(void)
{
return RakNet::OP_NEW<CloudQueryRow>(_FILE_AND_LINE_);
}
void CloudAllocator::DeallocateCloudQueryRow(CloudQueryRow *row)
{
RakNet::OP_DELETE(row,_FILE_AND_LINE_);
}
unsigned char *CloudAllocator::AllocateRowData(uint32_t bytesNeededForData)
{
return (unsigned char*) rakMalloc_Ex(bytesNeededForData,_FILE_AND_LINE_);
}
void CloudAllocator::DeallocateRowData(void *data)
{
rakFree_Ex(data, _FILE_AND_LINE_);
}
void CloudKey::Serialize(bool writeToBitstream, BitStream *bitStream)
{
bitStream->Serialize(writeToBitstream, primaryKey);
bitStream->Serialize(writeToBitstream, secondaryKey);
}
void CloudQuery::Serialize(bool writeToBitstream, BitStream *bitStream)
{
bool startingRowIndexIsZero=0;
bool maxRowsToReturnIsZero=0;
startingRowIndexIsZero=startingRowIndex==0;
maxRowsToReturnIsZero=maxRowsToReturn==0;
bitStream->Serialize(writeToBitstream,startingRowIndexIsZero);
bitStream->Serialize(writeToBitstream,maxRowsToReturnIsZero);
bitStream->Serialize(writeToBitstream,subscribeToResults);
if (startingRowIndexIsZero==false)
bitStream->Serialize(writeToBitstream,startingRowIndex);
if (maxRowsToReturnIsZero==false)
bitStream->Serialize(writeToBitstream,maxRowsToReturn);
RakAssert(keys.Size()<(uint16_t)-1);
uint16_t numKeys = (uint16_t) keys.Size();
bitStream->Serialize(writeToBitstream,numKeys);
if (writeToBitstream)
{
for (uint16_t i=0; i < numKeys; i++)
{
keys[i].Serialize(true,bitStream);
}
}
else
{
CloudKey cmdk;
for (uint16_t i=0; i < numKeys; i++)
{
cmdk.Serialize(false,bitStream);
keys.Push(cmdk, _FILE_AND_LINE_);
}
}
}
void CloudQueryRow::Serialize(bool writeToBitstream, BitStream *bitStream, CloudAllocator *allocator)
{
key.Serialize(writeToBitstream,bitStream);
bitStream->Serialize(writeToBitstream,serverSystemAddress);
bitStream->Serialize(writeToBitstream,clientSystemAddress);
bitStream->Serialize(writeToBitstream,serverGUID);
bitStream->Serialize(writeToBitstream,clientGUID);
bitStream->Serialize(writeToBitstream,length);
if (writeToBitstream)
{
bitStream->WriteAlignedBytes((const unsigned char*) data,length);
}
else
{
if (length>0)
{
data = allocator->AllocateRowData(length);
if (data)
{
bitStream->ReadAlignedBytes((unsigned char *) data,length);
}
else
{
notifyOutOfMemory(_FILE_AND_LINE_);
}
}
else
data=0;
}
}
void CloudQueryResult::SerializeHeader(bool writeToBitstream, BitStream *bitStream)
{
cloudQuery.Serialize(writeToBitstream,bitStream);
bitStream->Serialize(writeToBitstream,subscribeToResults);
}
void CloudQueryResult::SerializeNumRows(bool writeToBitstream, uint32_t &numRows, BitStream *bitStream)
{
bitStream->Serialize(writeToBitstream,numRows);
}
void CloudQueryResult::SerializeCloudQueryRows(bool writeToBitstream, uint32_t &numRows, BitStream *bitStream, CloudAllocator *allocator)
{
if (writeToBitstream)
{
for (uint16_t i=0; i < numRows; i++)
{
rowsReturned[i]->Serialize(true,bitStream, allocator);
}
}
else
{
CloudQueryRow* cmdr;
for (uint16_t i=0; i < numRows; i++)
{
cmdr = allocator->AllocateCloudQueryRow();
if (cmdr)
{
cmdr->Serialize(false,bitStream,allocator);
if (cmdr->data==0 && cmdr->length>0)
{
allocator->DeallocateCloudQueryRow(cmdr);
notifyOutOfMemory(_FILE_AND_LINE_);
numRows=i;
return;
}
rowsReturned.Push(cmdr, _FILE_AND_LINE_);
}
else
{
notifyOutOfMemory(_FILE_AND_LINE_);
numRows=i;
return;
}
}
}
}
void CloudQueryResult::Serialize(bool writeToBitstream, BitStream *bitStream, CloudAllocator *allocator)
{
SerializeHeader(writeToBitstream, bitStream);
uint32_t numRows = (uint32_t) rowsReturned.Size();
SerializeNumRows(writeToBitstream, numRows, bitStream);
SerializeCloudQueryRows(writeToBitstream, numRows, bitStream, allocator);
}
#endif // #if _RAKNET_SUPPORT_CloudMemoryClient==1 || _RAKNET_SUPPORT_CloudMemoryServer==1

View File

@@ -0,0 +1,141 @@
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_CloudClient==1 || _RAKNET_SUPPORT_CloudServer==1
#ifndef __CLOUD_COMMON_H
#define __CLOUD_COMMON_H
#include "RakNetTypes.h"
#include "RakString.h"
namespace RakNet
{
class BitStream;
struct CloudQueryRow;
/// Allocates CloudQueryRow and the row data. Override to use derived classes or different allocators
/// \ingroup CLOUD_GROUP
class RAK_DLL_EXPORT CloudAllocator
{
public:
CloudAllocator() {}
virtual ~CloudAllocator() {}
/// \brief Allocate a row
virtual CloudQueryRow* AllocateCloudQueryRow(void);
/// \brief Free a row
virtual void DeallocateCloudQueryRow(CloudQueryRow *row);
/// \brief Allocate CloudQueryRow::data
virtual unsigned char *AllocateRowData(uint32_t bytesNeededForData);
/// \brief Free CloudQueryRow::data
virtual void DeallocateRowData(void *data);
};
/// Serves as a key to identify data uploaded to or queried from the server.
/// \ingroup CLOUD_GROUP
struct RAK_DLL_EXPORT CloudKey
{
CloudKey() {}
CloudKey(RakNet::RakString _primaryKey, uint32_t _secondaryKey) : primaryKey(_primaryKey), secondaryKey(_secondaryKey) {}
~CloudKey() {}
/// Identifies the primary key. This is intended to be a major category, such as the name of the application
/// Must be non-empty
RakNet::RakString primaryKey;
/// Identifies the secondary key. This is intended to be a subcategory enumeration, such as PLAYER_LIST or RUNNING_SCORES
uint32_t secondaryKey;
/// \internal
void Serialize(bool writeToBitstream, BitStream *bitStream);
};
/// \internal
int CloudKeyComp(const CloudKey &key, const CloudKey &data);
/// Data members used to query the cloud
/// \ingroup CLOUD_GROUP
struct RAK_DLL_EXPORT CloudQuery
{
CloudQuery() {startingRowIndex=0; maxRowsToReturn=0; subscribeToResults=false;}
/// List of keys to query. Must be at least of length 1.
/// This query is run on uploads from all clients, and those that match the combination of primaryKey and secondaryKey are potentially returned
/// If you pass more than one key at a time, the results are concatenated so if you need to differentiate between queries then send two different queries
DataStructures::List<CloudKey> keys;
/// If limiting the number of rows to return, this is the starting offset into the list. Has no effect unless maxRowsToReturn is > 0
uint32_t startingRowIndex;
/// Maximum number of rows to return. Actual number may still be less than this. Pass 0 to mean no-limit.
uint32_t maxRowsToReturn;
/// If true, automatically get updates as the results returned to you change. Unsubscribe with CloudMemoryClient::Unsubscribe()
bool subscribeToResults;
/// \internal
void Serialize(bool writeToBitstream, BitStream *bitStream);
};
/// \ingroup CLOUD_GROUP
struct RAK_DLL_EXPORT CloudQueryRow
{
/// Key used to identify this data
CloudKey key;
/// Data uploaded
unsigned char *data;
/// Length of data uploaded
uint32_t length;
/// System address of server that is holding this data, and the client is connected to
SystemAddress serverSystemAddress;
/// System address of client that uploaded this data
SystemAddress clientSystemAddress;
/// RakNetGUID of server that is holding this data, and the client is connected to
RakNetGUID serverGUID;
/// RakNetGUID of client that uploaded this data
RakNetGUID clientGUID;
/// \internal
void Serialize(bool writeToBitstream, BitStream *bitStream, CloudAllocator *allocator);
};
/// \ingroup CLOUD_GROUP
struct RAK_DLL_EXPORT CloudQueryResult
{
/// Query originally passed to Download()
CloudQuery cloudQuery;
/// Results returned from query. If there were multiple keys in CloudQuery::keys then see resultKeyIndices
DataStructures::List<CloudQueryRow*> rowsReturned;
/// If there were multiple keys in CloudQuery::keys, then each key is processed in order and the result concatenated to rowsReturned
/// The starting index of each query is written to resultKeyIndices
/// For example, if CloudQuery::keys had 4 keys, returning 3 rows, 0, rows, 5 rows, and 12 rows then
/// resultKeyIndices would be 0, 3, 3, 8
DataStructures::List<uint32_t> resultKeyIndices;
/// Whatever was passed to CloudClient::Get() as CloudQuery::subscribeToResults
bool subscribeToResults;
/// \internal
void Serialize(bool writeToBitstream, BitStream *bitStream, CloudAllocator *allocator);
/// \internal
void SerializeHeader(bool writeToBitstream, BitStream *bitStream);
/// \internal
void SerializeNumRows(bool writeToBitstream, uint32_t &numRows, BitStream *bitStream);
/// \internal
void SerializeCloudQueryRows(bool writeToBitstream, uint32_t &numRows, BitStream *bitStream, CloudAllocator *allocator);
};
} // Namespace RakNet
#endif // __CLOUD_COMMON_H
#endif // #if _RAKNET_SUPPORT_CloudClient==1 || _RAKNET_SUPPORT_CloudServer==1

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,375 @@
/// \file CloudServer.h
/// \brief Stores client data, and allows cross-server communication to retrieve this data
/// \details TODO
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_CloudServer==1
#ifndef __CLOUD_SERVER_H
#define __CLOUD_SERVER_H
#include "PluginInterface2.h"
#include "RakMemoryOverride.h"
#include "NativeTypes.h"
#include "RakString.h"
#include "DS_Hash.h"
#include "CloudCommon.h"
#include "DS_OrderedList.h"
/// If the data is smaller than this value, an allocation is avoid. However, this value exists for every row
#define CLOUD_SERVER_DATA_STACK_SIZE 32
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
/// \brief Zero or more instances of CloudServerQueryFilter can be attached to CloudServer to restrict client queries
/// All attached instances of CloudServerQueryFilter on each corresponding operation, from all directly connected clients
/// If any attached instance returns false for a given operation, that operation is silently rejected
/// \ingroup CLOUD_GROUP
class RAK_DLL_EXPORT CloudServerQueryFilter
{
public:
CloudServerQueryFilter() {}
virtual ~CloudServerQueryFilter() {}
/// Called when a local client wants to post data
/// \return true to allow, false to reject
virtual bool OnPostRequest(RakNetGUID clientGuid, SystemAddress clientAddress, CloudKey key, uint32_t dataLength, const char *data)=0;
/// Called when a local client wants to release data that it has previously uploaded
/// \return true to allow, false to reject
virtual bool OnReleaseRequest(RakNetGUID clientGuid, SystemAddress clientAddress, DataStructures::List<CloudKey> &cloudKeys)=0;
/// Called when a local client wants to query data
/// If you return false, the client will get no response at all
/// \return true to allow, false to reject
virtual bool OnGetRequest(RakNetGUID clientGuid, SystemAddress clientAddress, CloudQuery &query, DataStructures::List<RakNetGUID> &specificSystems)=0;
/// Called when a local client wants to stop getting updates for data
/// If you return false, the client will keep getting updates for that data
/// \return true to allow, false to reject
virtual bool OnUnsubscribeRequest(RakNetGUID clientGuid, SystemAddress clientAddress, DataStructures::List<CloudKey> &cloudKeys, DataStructures::List<RakNetGUID> &specificSystems)=0;
};
/// \brief Stores client data, and allows cross-server communication to retrieve this data
/// \ingroup CLOUD_GROUP
class RAK_DLL_EXPORT CloudServer : public PluginInterface2, CloudAllocator
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(CloudServer)
CloudServer();
virtual ~CloudServer();
/// \brief Max bytes a client can upload
/// Data in excess of this value is silently ignored
/// defaults to 0 (unlimited)
/// \param[in] bytes Max bytes a client can upload. 0 means unlimited.
void SetMaxUploadBytesPerClient(uint64_t bytes);
/// \brief Max bytes returned by a download. If the number of bytes would exceed this amount, the returned list is truncated
/// However, if this would result in no rows downloaded, then one row will be returned.
/// \param[in] bytes Max bytes a client can download from a single Get(). 0 means unlimited.
void SetMaxBytesPerDownload(uint64_t bytes);
/// \brief Add a server, which is assumed to be connected in a fully connected mesh to all other servers and also running the CloudServer plugin
/// The other system must also call AddServer before getting the subscription data, or it will be rejected.
/// Sending a message telling the other system to call AddServer(), followed by calling AddServer() locally, would be sufficient for this to work.
/// \note This sends subscription data to the other system, using RELIABLE_ORDERED on channel 0
/// \param[in] systemIdentifier Identifier of the remote system
void AddServer(RakNetGUID systemIdentifier);
/// \brief Removes a server added through AddServer()
/// \param[in] systemIdentifier Identifier of the remote system
void RemoveServer(RakNetGUID systemIdentifier);
/// Return list of servers added with AddServer()
/// \param[out] remoteServers List of servers added
void GetRemoteServers(DataStructures::List<RakNetGUID> &remoteServersOut);
/// \brief Frees all memory. Does not remove query filters
void Clear(void);
/// \brief Report the specified SystemAddress to client queries, rather than what RakPeer reads.
/// This is useful if you already know your public IP
/// This only applies to future updates, so call it before updating to apply to all queries
/// \param[in] forcedAddress The systmeAddress to return in queries. Use UNASSIGNED_SYSTEM_ADDRESS (default) to use what RakPeer returns
void ForceExternalSystemAddress(SystemAddress forcedAddress);
/// \brief Adds a callback called on each query. If all filters returns true for an operation, the operation is allowed.
/// If the filter was already added, the function silently fails
/// \param[in] filter An externally allocated instance of CloudServerQueryFilter. The instance must remain valid until it is removed with RemoveQueryFilter() or RemoveAllQueryFilters()
void AddQueryFilter(CloudServerQueryFilter* filter);
/// \brief Removes a callback added with AddQueryFilter()
/// The instance is not deleted, only unreferenced. It is up to the user to delete the instance, if necessary
/// \param[in] filter An externally allocated instance of CloudServerQueryFilter. The instance must remain valid until it is removed with RemoveQueryFilter() or RemoveAllQueryFilters()
void RemoveQueryFilter(CloudServerQueryFilter* filter);
/// \brief Removes all instances of CloudServerQueryFilter added with AddQueryFilter().
/// The instances are not deleted, only unreferenced. It is up to the user to delete the instances, if necessary
void RemoveAllQueryFilters(void);
protected:
virtual void Update(void);
virtual PluginReceiveResult OnReceive(Packet *packet);
virtual void OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason );
virtual void OnRakPeerShutdown(void);
virtual void OnPostRequest(Packet *packet);
virtual void OnReleaseRequest(Packet *packet);
virtual void OnGetRequest(Packet *packet);
virtual void OnUnsubscribeRequest(Packet *packet);
virtual void OnServerToServerGetRequest(Packet *packet);
virtual void OnServerToServerGetResponse(Packet *packet);
uint64_t maxUploadBytesPerClient, maxBytesPerDowload;
// ----------------------------------------------------------------------------
// For a given data key, quickly look up one or all systems that have uploaded
// ----------------------------------------------------------------------------
struct CloudData
{
CloudData() {}
~CloudData() {if (allocatedData) rakFree_Ex(allocatedData, _FILE_AND_LINE_);}
bool IsUnused(void) const {return isUploaded==false && specificSubscribers.Size()==0;}
void Clear(void) {if (dataPtr==allocatedData) rakFree_Ex(allocatedData, _FILE_AND_LINE_); allocatedData=0; dataPtr=0; dataLengthBytes=0; isUploaded=false;}
unsigned char stackData[CLOUD_SERVER_DATA_STACK_SIZE];
unsigned char *allocatedData; // Uses allocatedData instead of stackData if length of data exceeds CLOUD_SERVER_DATA_STACK_SIZE
unsigned char *dataPtr; // Points to either stackData or allocatedData
uint32_t dataLengthBytes;
bool isUploaded;
/// System address of server that is holding this data, and the client is connected to
SystemAddress serverSystemAddress;
/// System address of client that uploaded this data
SystemAddress clientSystemAddress;
/// RakNetGUID of server that is holding this data, and the client is connected to
RakNetGUID serverGUID;
/// RakNetGUID of client that uploaded this data
RakNetGUID clientGUID;
/// When the key data changes from this particular system, notify these subscribers
/// This list mutually exclusive with CloudDataList::nonSpecificSubscribers
DataStructures::OrderedList<RakNetGUID, RakNetGUID> specificSubscribers;
};
void WriteCloudQueryRowFromResultList(unsigned int i, DataStructures::List<CloudData*> &cloudDataResultList, DataStructures::List<CloudKey> &cloudKeyResultList, BitStream *bsOut);
void WriteCloudQueryRowFromResultList(DataStructures::List<CloudData*> &cloudDataResultList, DataStructures::List<CloudKey> &cloudKeyResultList, BitStream *bsOut);
static int KeyDataPtrComp( const RakNetGUID &key, CloudData* const &data );
struct CloudDataList
{
bool IsUnused(void) const {return keyData.Size()==0 && nonSpecificSubscribers.Size()==0;}
bool IsNotUploaded(void) const {return uploaderCount==0;}
bool RemoveSubscriber(RakNetGUID g) {
bool objectExists;
unsigned int index;
index = nonSpecificSubscribers.GetIndexFromKey(g, &objectExists);
if (objectExists)
{
subscriberCount--;
nonSpecificSubscribers.RemoveAtIndex(index);
return true;
}
return false;
}
unsigned int uploaderCount, subscriberCount;
CloudKey key;
// Data uploaded from or subscribed to for various systems
DataStructures::OrderedList<RakNetGUID, CloudData*, CloudServer::KeyDataPtrComp> keyData;
/// When the key data changes from any system, notify these subscribers
/// This list mutually exclusive with CloudData::specificSubscribers
DataStructures::OrderedList<RakNetGUID, RakNetGUID> nonSpecificSubscribers;
};
static int KeyDataListComp( const CloudKey &key, CloudDataList * const &data );
DataStructures::OrderedList<CloudKey, CloudDataList*, CloudServer::KeyDataListComp> dataRepository;
struct KeySubscriberID
{
CloudKey key;
DataStructures::OrderedList<RakNetGUID, RakNetGUID> specificSystemsSubscribedTo;
};
static int KeySubscriberIDComp(const CloudKey &key, KeySubscriberID * const &data );
// Remote systems
struct RemoteCloudClient
{
bool IsUnused(void) const {return uploadedKeys.Size()==0 && subscribedKeys.Size()==0;}
DataStructures::OrderedList<CloudKey,CloudKey,CloudKeyComp> uploadedKeys;
DataStructures::OrderedList<CloudKey,KeySubscriberID*,CloudServer::KeySubscriberIDComp> subscribedKeys;
uint64_t uploadedBytes;
};
DataStructures::Hash<RakNetGUID, RemoteCloudClient*, 2048, RakNetGUID::ToUint32> remoteSystems;
// For a given user, release all subscribed and uploaded keys
void ReleaseSystem(RakNetGUID clientAddress );
// For a given user, release a set of keys
void ReleaseKeys(RakNetGUID clientAddress, DataStructures::List<CloudKey> &keys );
void NotifyClientSubscribersOfDataChange( CloudData *cloudData, CloudKey &key, DataStructures::OrderedList<RakNetGUID, RakNetGUID> &subscribers, bool wasUpdated );
void NotifyClientSubscribersOfDataChange( CloudQueryRow *row, DataStructures::OrderedList<RakNetGUID, RakNetGUID> &subscribers, bool wasUpdated );
void NotifyServerSubscribersOfDataChange( CloudData *cloudData, CloudKey &key, bool wasUpdated );
struct RemoteServer
{
RakNetGUID serverAddress;
// This server needs to know about these keys when they are updated or deleted
DataStructures::OrderedList<CloudKey,CloudKey,CloudKeyComp> subscribedKeys;
// This server has uploaded these keys, and needs to know about Get() requests
DataStructures::OrderedList<CloudKey,CloudKey,CloudKeyComp> uploadedKeys;
// Just for processing
bool workingFlag;
// If false, we don't know what keys they have yet, so send everything
bool gotSubscribedAndUploadedKeys;
};
static int RemoteServerComp(const RakNetGUID &key, RemoteServer* const &data );
DataStructures::OrderedList<RakNetGUID, RemoteServer*, CloudServer::RemoteServerComp> remoteServers;
struct BufferedGetResponseFromServer
{
void Clear(CloudAllocator *allocator);
RakNetGUID serverAddress;
CloudQueryResult queryResult;
bool gotResult;
};
struct CloudQueryWithAddresses
{
// Inputs
CloudQuery cloudQuery;
DataStructures::List<RakNetGUID> specificSystems;
void Serialize(bool writeToBitstream, BitStream *bitStream);
};
static int BufferedGetResponseFromServerComp(const RakNetGUID &key, BufferedGetResponseFromServer* const &data );
struct GetRequest
{
void Clear(CloudAllocator *allocator);
bool AllRemoteServersHaveResponded(void) const;
CloudQueryWithAddresses cloudQueryWithAddresses;
// When request started. If takes too long for a response from another system, can abort remaining systems
RakNet::Time requestStartTime;
// Assigned by server that gets the request to identify response. See nextGetRequestId
uint32_t requestId;
RakNetGUID requestingClient;
DataStructures::OrderedList<RakNetGUID, BufferedGetResponseFromServer*, CloudServer::BufferedGetResponseFromServerComp> remoteServerResponses;
};
static int GetRequestComp(const uint32_t &key, GetRequest* const &data );
DataStructures::OrderedList<uint32_t, GetRequest*, CloudServer::GetRequestComp> getRequests;
RakNet::Time nextGetRequestsCheck;
uint32_t nextGetRequestId;
void ProcessAndTransmitGetRequest(GetRequest *getRequest);
void ProcessCloudQueryWithAddresses(
CloudServer::CloudQueryWithAddresses &cloudQueryWithAddresses,
DataStructures::List<CloudData*> &cloudDataResultList,
DataStructures::List<CloudKey> &cloudKeyResultList
);
void SendUploadedAndSubscribedKeysToServer( RakNetGUID systemAddress );
void SendUploadedKeyToServers( CloudKey &cloudKey );
void SendSubscribedKeyToServers( CloudKey &cloudKey );
void RemoveUploadedKeyFromServers( CloudKey &cloudKey );
void RemoveSubscribedKeyFromServers( CloudKey &cloudKey );
void OnSendUploadedAndSubscribedKeysToServer( Packet *packet );
void OnSendUploadedKeyToServers( Packet *packet );
void OnSendSubscribedKeyToServers( Packet *packet );
void OnRemoveUploadedKeyFromServers( Packet *packet );
void OnRemoveSubscribedKeyFromServers( Packet *packet );
void OnServerDataChanged( Packet *packet );
void GetServersWithUploadedKeys(
DataStructures::List<CloudKey> &keys,
DataStructures::List<RemoteServer*> &remoteServersWithData
);
CloudServer::CloudDataList *GetOrAllocateCloudDataList(CloudKey key, bool *dataRepositoryExists, unsigned int &dataRepositoryIndex);
void UnsubscribeFromKey(RemoteCloudClient *remoteCloudClient, RakNetGUID remoteCloudClientGuid, unsigned int keySubscriberIndex, CloudKey &cloudKey, DataStructures::List<RakNetGUID> &specificSystems);
void RemoveSpecificSubscriber(RakNetGUID specificSubscriber, CloudDataList *cloudDataList, RakNetGUID remoteCloudClientGuid);
DataStructures::List<CloudServerQueryFilter*> queryFilters;
SystemAddress forceAddress;
};
} // namespace RakNet
#endif
// Key subscription
//
// A given system can subscribe to one or more keys.
// The subscription can be further be defined as only subscribing to keys uploaded by or changed by a given system.
// It is possible to subscribe to keys not yet uploaded, or uploaded to another system
//
// Operations:
//
// 1. SubscribeToKey() - Get() operation with subscription
// A. Add to key subscription list for the client, which contains a keyId / specificUploaderList pair
// B. Send to remote servers that for this key, they should send us updates
// C. (Done, get operation returns current values)
//
// 2. UpdateData() - Post() operation
// A. Find all subscribers to this data, for the uploading system.
// B. Send them the uploaded data
// C. Find all servers that subscribe to this data
// D. Send them the uploaded data
//
// 3. DeleteData() - Release() operation
// A. Find all subscribers to this data, for the deleting system.
// B. Inform them of the deletion
// C. Find all servers that subscribe to this data
// D. Inform them of the deletion
//
// 4. Unsubscribe()
// A. Find this subscriber, and remove their subscription
// B. If no one else is subscribing to this key for any system, notify remote servers we no longer need subscription updates
//
// Internal operations:
//
// 1. Find if any connected client has subscribed to a given key
// A. This is used add and remove our subscription for this key to remote servers
//
// 2. For a given key and updating address, find all connected clients that care
// A. First find connected clients that have subscribed to this key, regardless of address
// B. Then find connected clients that have subscribed to this key for this particular address
//
// 3. Find all remote servers that have subscribed to a given key
// A. This is so when the key is updated or deleted, we know who to send it to
//
// 4. For a given client (such as on disconnect), remove all records of their subscriptions
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,161 @@
#include "CommandParserInterface.h"
#include "TransportInterface.h"
#include <string.h>
#include "RakAssert.h"
#include <stdio.h>
#if defined(_WIN32)
// IP_DONTFRAGMENT is different between winsock 1 and winsock 2. Therefore, Winsock2.h must be linked againt Ws2_32.lib
// winsock.h must be linked against WSock32.lib. If these two are mixed up the flag won't work correctly
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include "LinuxStrings.h"
using namespace RakNet;
#ifdef _MSC_VER
#pragma warning( push )
#endif
const unsigned char CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS=255;
int RakNet::RegisteredCommandComp( const char* const & key, const RegisteredCommand &data )
{
return _stricmp(key,data.command);
}
CommandParserInterface::CommandParserInterface() {}
CommandParserInterface::~CommandParserInterface() {}
void CommandParserInterface::ParseConsoleString(char *str, const char delineator, unsigned char delineatorToggle, unsigned *numParameters, char **parameterList, unsigned parameterListLength)
{
unsigned strIndex, parameterListIndex;
unsigned strLen;
bool replaceDelineator=true;
strLen = (unsigned) strlen(str);
// Replace every instance of delineator, \n, \r with 0
for (strIndex=0; strIndex < strLen; strIndex++)
{
if (str[strIndex]==delineator && replaceDelineator)
str[strIndex]=0;
if (str[strIndex]=='\n' || str[strIndex]=='\r')
str[strIndex]=0;
if (str[strIndex]==delineatorToggle)
{
str[strIndex]=0;
replaceDelineator=!replaceDelineator;
}
}
// Fill up parameterList starting at each non-0
for (strIndex=0, parameterListIndex=0; strIndex < strLen; )
{
if (str[strIndex]!=0)
{
parameterList[parameterListIndex]=str+strIndex;
parameterListIndex++;
RakAssert(parameterListIndex < parameterListLength);
if (parameterListIndex >= parameterListLength)
break;
strIndex++;
while (str[strIndex]!=0 && strIndex < strLen)
strIndex++;
}
else
strIndex++;
}
parameterList[parameterListIndex]=0;
*numParameters=parameterListIndex;
}
void CommandParserInterface::SendCommandList(TransportInterface *transport, const SystemAddress &systemAddress)
{
unsigned i;
if (commandList.Size())
{
for (i=0; i < commandList.Size(); i++)
{
transport->Send(systemAddress, "%s", commandList[i].command);
if (i < commandList.Size()-1)
transport->Send(systemAddress, ", ");
}
transport->Send(systemAddress, "\r\n");
}
else
transport->Send(systemAddress, "No registered commands\r\n");
}
void CommandParserInterface::RegisterCommand(unsigned char parameterCount, const char *command, const char *commandHelp)
{
RegisteredCommand rc;
rc.command=command;
rc.commandHelp=commandHelp;
rc.parameterCount=parameterCount;
commandList.Insert( command, rc, true, _FILE_AND_LINE_);
}
bool CommandParserInterface::GetRegisteredCommand(const char *command, RegisteredCommand *rc)
{
bool objectExists;
unsigned index;
index=commandList.GetIndexFromKey(command, &objectExists);
if (objectExists)
*rc=commandList[index];
return objectExists;
}
void CommandParserInterface::OnTransportChange(TransportInterface *transport)
{
(void) transport;
}
void CommandParserInterface::OnNewIncomingConnection(const SystemAddress &systemAddress, TransportInterface *transport)
{
(void) systemAddress;
(void) transport;
}
void CommandParserInterface::OnConnectionLost(const SystemAddress &systemAddress, TransportInterface *transport)
{
(void) systemAddress;
(void) transport;
}
void CommandParserInterface::ReturnResult(bool res, const char *command,TransportInterface *transport, const SystemAddress &systemAddress)
{
if (res)
transport->Send(systemAddress, "%s returned true.\r\n", command);
else
transport->Send(systemAddress, "%s returned false.\r\n", command);
}
void CommandParserInterface::ReturnResult(int res, const char *command,TransportInterface *transport, const SystemAddress &systemAddress)
{
transport->Send(systemAddress, "%s returned %i.\r\n", command, res);
}
void CommandParserInterface::ReturnResult(const char *command, TransportInterface *transport, const SystemAddress &systemAddress)
{
transport->Send(systemAddress, "Successfully called %s.\r\n", command);
}
void CommandParserInterface::ReturnResult(char *res, const char *command, TransportInterface *transport, const SystemAddress &systemAddress)
{
transport->Send(systemAddress, "%s returned %s.\r\n", command, res);
}
void CommandParserInterface::ReturnResult(SystemAddress res, const char *command, TransportInterface *transport, const SystemAddress &systemAddress)
{
char addr[128];
systemAddress.ToString(false,addr);
char addr2[128];
res.ToString(false,addr2);
transport->Send(systemAddress, "%s returned %s %s:%i\r\n", command,addr,addr2,res.GetPort());
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif

View File

@@ -0,0 +1,140 @@
/// \file CommandParserInterface.h
/// \brief Contains CommandParserInterface , from which you derive custom command parsers
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __COMMAND_PARSER_INTERFACE
#define __COMMAND_PARSER_INTERFACE
#include "RakMemoryOverride.h"
#include "RakNetTypes.h"
#include "DS_OrderedList.h"
#include "Export.h"
namespace RakNet
{
/// Forward declarations
class TransportInterface;
/// \internal
/// Contains the information related to one command registered with RegisterCommand()
/// Implemented so I can have an automatic help system via SendCommandList()
struct RAK_DLL_EXPORT RegisteredCommand
{
const char *command;
const char *commandHelp;
unsigned char parameterCount;
};
/// List of commands registered with RegisterCommand()
int RAK_DLL_EXPORT RegisteredCommandComp( const char* const & key, const RegisteredCommand &data );
/// \brief The interface used by command parsers.
/// \details CommandParserInterface provides a set of functions and interfaces that plug into the ConsoleServer class.
/// Each CommandParserInterface works at the same time as other interfaces in the system.
class RAK_DLL_EXPORT CommandParserInterface
{
public:
CommandParserInterface();
virtual ~CommandParserInterface();
/// You are responsible for overriding this function and returning a static string, which will identifier your parser.
/// This should return a static string
/// \return The name that you return.
virtual const char *GetName(void) const=0;
/// \brief A callback for when \a systemAddress has connected to us.
/// \param[in] systemAddress The player that has connected.
/// \param[in] transport The transport interface that sent us this information. Can be used to send messages to this or other players.
virtual void OnNewIncomingConnection(const SystemAddress &systemAddress, TransportInterface *transport);
/// \brief A callback for when \a systemAddress has disconnected, either gracefully or forcefully
/// \param[in] systemAddress The player that has disconnected.
/// \param[in] transport The transport interface that sent us this information.
virtual void OnConnectionLost(const SystemAddress &systemAddress, TransportInterface *transport);
/// \brief A callback for when you are expected to send a brief description of your parser to \a systemAddress
/// \param[in] transport The transport interface we can use to write to
/// \param[in] systemAddress The player that requested help.
virtual void SendHelp(TransportInterface *transport, const SystemAddress &systemAddress)=0;
/// \brief Given \a command with parameters \a parameterList , do whatever processing you wish.
/// \param[in] command The command to process
/// \param[in] numParameters How many parameters were passed along with the command
/// \param[in] parameterList The list of parameters. parameterList[0] is the first parameter and so on.
/// \param[in] transport The transport interface we can use to write to
/// \param[in] systemAddress The player that sent this command.
/// \param[in] originalString The string that was actually sent over the network, in case you want to do your own parsing
virtual bool OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, const SystemAddress &systemAddress, const char *originalString)=0;
/// \brief This is called every time transport interface is registered.
/// \details If you want to save a copy of the TransportInterface pointer
/// This is the place to do it
/// \param[in] transport The new TransportInterface
virtual void OnTransportChange(TransportInterface *transport);
/// \internal
/// Scan commandList and return the associated array
/// \param[in] command The string to find
/// \param[out] rc Contains the result of this operation
/// \return True if we found the command, false otherwise
virtual bool GetRegisteredCommand(const char *command, RegisteredCommand *rc);
/// \internal
/// Goes through str, replacing the delineating character with 0's.
/// \param[in] str The string sent by the transport interface
/// \param[in] delineator The character to scan for to use as a delineator
/// \param[in] delineatorToggle When encountered the delineator replacement is toggled on and off
/// \param[out] numParameters How many pointers were written to \a parameterList
/// \param[out] parameterList An array of pointers to characters. Will hold pointers to locations inside \a str
/// \param[in] parameterListLength How big the \a parameterList array is
static void ParseConsoleString(char *str, const char delineator, unsigned char delineatorToggle, unsigned *numParameters, char **parameterList, unsigned parameterListLength);
/// \internal
/// Goes through the variable commandList and sends the command portion of each struct
/// \param[in] transport The transport interface we can use to write to
/// \param[in] systemAddress The player to write to
virtual void SendCommandList(TransportInterface *transport, const SystemAddress &systemAddress);
static const unsigned char VARIABLE_NUMBER_OF_PARAMETERS;
// Currently only takes static strings - doesn't make a copy of what you pass.
// parameterCount is the number of parameters that the sender has to include with the command.
// Pass 255 to parameterCount to indicate variable number of parameters
/// Registers a command.
/// \param[in] parameterCount How many parameters your command requires. If you want to accept a variable number of commands, pass CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS
/// \param[in] command A pointer to a STATIC string that has your command. I keep a copy of the pointer here so don't deallocate the string.
/// \param[in] commandHelp A pointer to a STATIC string that has the help information for your command. I keep a copy of the pointer here so don't deallocate the string.
virtual void RegisterCommand(unsigned char parameterCount, const char *command, const char *commandHelp);
/// \brief Just writes a string to the remote system based on the result ( \a res ) of your operation
/// \details This is not necessary to call, but makes it easier to return results of function calls.
/// \param[in] res The result to write
/// \param[in] command The command that this result came from
/// \param[in] transport The transport interface that will be written to
/// \param[in] systemAddress The player this result will be sent to
virtual void ReturnResult(bool res, const char *command, TransportInterface *transport, const SystemAddress &systemAddress);
virtual void ReturnResult(char *res, const char *command, TransportInterface *transport, const SystemAddress &systemAddress);
virtual void ReturnResult(SystemAddress res, const char *command, TransportInterface *transport, const SystemAddress &systemAddress);
virtual void ReturnResult(int res, const char *command,TransportInterface *transport, const SystemAddress &systemAddress);
/// \brief Just writes a string to the remote system when you are calling a function that has no return value.
/// \details This is not necessary to call, but makes it easier to return results of function calls.
/// \param[in] res The result to write
/// \param[in] command The command that this result came from
/// \param[in] transport The transport interface that will be written to
/// \param[in] systemAddress The player this result will be sent to
virtual void ReturnResult(const char *command,TransportInterface *transport, const SystemAddress &systemAddress);
protected:
DataStructures::OrderedList<const char*, RegisteredCommand, RegisteredCommandComp> commandList;
};
} // namespace RakNet
#endif

View File

@@ -0,0 +1,299 @@
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_ConnectionGraph2==1
#include "ConnectionGraph2.h"
#include "RakPeerInterface.h"
#include "MessageIdentifiers.h"
#include "BitStream.h"
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(ConnectionGraph2,ConnectionGraph2);
int RakNet::ConnectionGraph2::RemoteSystemComp( const RakNetGUID &key, RemoteSystem * const &data )
{
if (key < data->guid)
return -1;
if (key > data->guid)
return 1;
return 0;
}
int RakNet::ConnectionGraph2::SystemAddressAndGuidComp( const SystemAddressAndGuid &key, const SystemAddressAndGuid &data )
{
if (key.guid<data.guid)
return -1;
if (key.guid>data.guid)
return 1;
return 0;
}
ConnectionGraph2::ConnectionGraph2()
{
autoProcessNewConnections=true;
}
ConnectionGraph2::~ConnectionGraph2()
{
}
bool ConnectionGraph2::GetConnectionListForRemoteSystem(RakNetGUID remoteSystemGuid, SystemAddress *saOut, RakNetGUID *guidOut, unsigned int *outLength)
{
if ((saOut==0 && guidOut==0) || outLength==0 || *outLength==0 || remoteSystemGuid==UNASSIGNED_RAKNET_GUID)
{
*outLength=0;
return false;
}
bool objectExists;
unsigned int idx = remoteSystems.GetIndexFromKey(remoteSystemGuid, &objectExists);
if (objectExists==false)
{
*outLength=0;
return false;
}
unsigned int idx2;
if (remoteSystems[idx]->remoteConnections.Size() < *outLength)
*outLength=remoteSystems[idx]->remoteConnections.Size();
for (idx2=0; idx2 < *outLength; idx2++)
{
if (guidOut)
guidOut[idx2]=remoteSystems[idx]->remoteConnections[idx2].guid;
if (saOut)
saOut[idx2]=remoteSystems[idx]->remoteConnections[idx2].systemAddress;
}
return true;
}
bool ConnectionGraph2::ConnectionExists(RakNetGUID g1, RakNetGUID g2)
{
if (g1==g2)
return false;
bool objectExists;
unsigned int idx = remoteSystems.GetIndexFromKey(g1, &objectExists);
if (objectExists==false)
{
return false;
}
SystemAddressAndGuid sag;
sag.guid=g2;
return remoteSystems[idx]->remoteConnections.HasData(sag);
}
uint16_t ConnectionGraph2::GetPingBetweenSystems(RakNetGUID g1, RakNetGUID g2) const
{
if (g1==g2)
return 0;
if (g1==rakPeerInterface->GetMyGUID())
return (uint16_t) rakPeerInterface->GetAveragePing(g2);
if (g2==rakPeerInterface->GetMyGUID())
return (uint16_t) rakPeerInterface->GetAveragePing(g1);
bool objectExists;
unsigned int idx = remoteSystems.GetIndexFromKey(g1, &objectExists);
if (objectExists==false)
{
return (uint16_t) -1;
}
SystemAddressAndGuid sag;
sag.guid=g2;
unsigned int idx2 = remoteSystems[idx]->remoteConnections.GetIndexFromKey(sag, &objectExists);
if (objectExists==false)
{
return (uint16_t) -1;
}
return remoteSystems[idx]->remoteConnections[idx2].sendersPingToThatSystem;
}
/// Returns the system with the lowest total ping among all its connections. This can be used as the 'best host' for a peer to peer session
RakNetGUID ConnectionGraph2::GetLowestAveragePingSystem(void) const
{
float lowestPing=-1.0;
unsigned int lowestPingIdx=(unsigned int) -1;
float thisAvePing=0.0f;
unsigned int idx, idx2;
int ap, count=0;
for (idx=0; idx<remoteSystems.Size(); idx++)
{
thisAvePing=0.0f;
ap = rakPeerInterface->GetAveragePing(remoteSystems[idx]->guid);
if (ap!=-1)
{
thisAvePing+=(float) ap;
count++;
}
}
if (count>0)
{
lowestPing=thisAvePing/count;
}
for (idx=0; idx<remoteSystems.Size(); idx++)
{
thisAvePing=0.0f;
count=0;
RemoteSystem *remoteSystem = remoteSystems[idx];
for (idx2=0; idx2 < remoteSystem->remoteConnections.Size(); idx2++)
{
ap=remoteSystem->remoteConnections[idx2].sendersPingToThatSystem;
if (ap!=-1)
{
thisAvePing+=(float) ap;
count++;
}
}
if (count>0 && (lowestPing==-1.0f || thisAvePing/count < lowestPing))
{
lowestPing=thisAvePing/count;
lowestPingIdx=idx;
}
}
if (lowestPingIdx==(unsigned int) -1)
return rakPeerInterface->GetMyGUID();
return remoteSystems[lowestPingIdx]->guid;
}
void ConnectionGraph2::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason )
{
// Send notice to all existing connections
RakNet::BitStream bs;
if (lostConnectionReason==LCR_CONNECTION_LOST)
bs.Write((MessageID)ID_REMOTE_CONNECTION_LOST);
else
bs.Write((MessageID)ID_REMOTE_DISCONNECTION_NOTIFICATION);
bs.Write(systemAddress);
bs.Write(rakNetGUID);
SendUnified(&bs,HIGH_PRIORITY,RELIABLE_ORDERED,0,systemAddress,true);
bool objectExists;
unsigned int idx = remoteSystems.GetIndexFromKey(rakNetGUID, &objectExists);
if (objectExists)
{
RakNet::OP_DELETE(remoteSystems[idx],_FILE_AND_LINE_);
remoteSystems.RemoveAtIndex(idx);
}
}
void ConnectionGraph2::SetAutoProcessNewConnections(bool b)
{
autoProcessNewConnections=b;
}
bool ConnectionGraph2::GetAutoProcessNewConnections(void) const
{
return autoProcessNewConnections;
}
void ConnectionGraph2::AddParticipant(const SystemAddress &systemAddress, RakNetGUID rakNetGUID)
{
// Relay the new connection to other systems.
RakNet::BitStream bs;
bs.Write((MessageID)ID_REMOTE_NEW_INCOMING_CONNECTION);
bs.Write((uint32_t)1);
bs.Write(systemAddress);
bs.Write(rakNetGUID);
bs.WriteCasted<uint16_t>(rakPeerInterface->GetAveragePing(rakNetGUID));
SendUnified(&bs,HIGH_PRIORITY,RELIABLE_ORDERED,0,systemAddress,true);
// Send everyone to the new guy
DataStructures::List<SystemAddress> addresses;
DataStructures::List<RakNetGUID> guids;
rakPeerInterface->GetSystemList(addresses, guids);
bs.Reset();
bs.Write((MessageID)ID_REMOTE_NEW_INCOMING_CONNECTION);
BitSize_t writeOffset = bs.GetWriteOffset();
bs.Write((uint32_t) addresses.Size());
unsigned int i;
uint32_t count=0;
for (i=0; i < addresses.Size(); i++)
{
if (addresses[i]==systemAddress)
continue;
bs.Write(addresses[i]);
bs.Write(guids[i]);
bs.WriteCasted<uint16_t>(rakPeerInterface->GetAveragePing(guids[i]));
count++;
}
if (count>0)
{
BitSize_t writeOffset2 = bs.GetWriteOffset();
bs.SetWriteOffset(writeOffset);
bs.Write(count);
bs.SetWriteOffset(writeOffset2);
SendUnified(&bs,HIGH_PRIORITY,RELIABLE_ORDERED,0,systemAddress,false);
}
bool objectExists;
unsigned int ii = remoteSystems.GetIndexFromKey(rakNetGUID, &objectExists);
if (objectExists==false)
{
RemoteSystem* remoteSystem = RakNet::OP_NEW<RemoteSystem>(_FILE_AND_LINE_);
remoteSystem->guid=rakNetGUID;
remoteSystems.InsertAtIndex(remoteSystem,ii,_FILE_AND_LINE_);
}
}
void ConnectionGraph2::GetParticipantList(DataStructures::OrderedList<RakNetGUID, RakNetGUID> &participantList)
{
participantList.Clear(true, _FILE_AND_LINE_);
unsigned int i;
for (i=0; i < remoteSystems.Size(); i++)
participantList.InsertAtEnd(remoteSystems[i]->guid, _FILE_AND_LINE_);
}
void ConnectionGraph2::OnNewConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, bool isIncoming)
{
(void) isIncoming;
if (autoProcessNewConnections)
AddParticipant(systemAddress, rakNetGUID);
}
PluginReceiveResult ConnectionGraph2::OnReceive(Packet *packet)
{
if (packet->data[0]==ID_REMOTE_CONNECTION_LOST || packet->data[0]==ID_REMOTE_DISCONNECTION_NOTIFICATION)
{
bool objectExists;
unsigned idx = remoteSystems.GetIndexFromKey(packet->guid, &objectExists);
if (objectExists)
{
RakNet::BitStream bs(packet->data,packet->length,false);
bs.IgnoreBytes(1);
SystemAddressAndGuid saag;
bs.Read(saag.systemAddress);
bs.Read(saag.guid);
unsigned long idx2 = remoteSystems[idx]->remoteConnections.GetIndexFromKey(saag, &objectExists);
if (objectExists)
remoteSystems[idx]->remoteConnections.RemoveAtIndex(idx2);
}
}
else if (packet->data[0]==ID_REMOTE_NEW_INCOMING_CONNECTION)
{
bool objectExists;
unsigned idx = remoteSystems.GetIndexFromKey(packet->guid, &objectExists);
if (objectExists)
{
uint32_t numAddresses;
RakNet::BitStream bs(packet->data,packet->length,false);
bs.IgnoreBytes(1);
bs.Read(numAddresses);
for (unsigned int idx2=0; idx2 < numAddresses; idx2++)
{
SystemAddressAndGuid saag;
bs.Read(saag.systemAddress);
bs.Read(saag.guid);
bs.Read(saag.sendersPingToThatSystem);
bool objectExists;
unsigned int ii = remoteSystems[idx]->remoteConnections.GetIndexFromKey(saag, &objectExists);
if (objectExists==false)
remoteSystems[idx]->remoteConnections.InsertAtIndex(saag,ii,_FILE_AND_LINE_);
}
}
}
return RR_CONTINUE_PROCESSING;
}
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,118 @@
/// \file ConnectionGraph2.h
/// \brief Connection graph plugin, version 2. Tells new systems about existing and new connections
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_ConnectionGraph2==1
#ifndef __CONNECTION_GRAPH_2_H
#define __CONNECTION_GRAPH_2_H
#include "RakMemoryOverride.h"
#include "RakNetTypes.h"
#include "PluginInterface2.h"
#include "DS_List.h"
#include "DS_WeightedGraph.h"
#include "GetTime.h"
#include "Export.h"
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
/// \brief A one hop connection graph.
/// \details Sends ID_REMOTE_CONNECTION_LOST, ID_REMOTE_DISCONNECTION_NOTIFICATION, ID_REMOTE_NEW_INCOMING_CONNECTION<BR>
/// All identifiers are followed by SystemAddress, then RakNetGUID
/// Also stores the list for you, which you can access with GetConnectionListForRemoteSystem
/// \ingroup CONNECTION_GRAPH_GROUP
class RAK_DLL_EXPORT ConnectionGraph2 : public PluginInterface2
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(ConnectionGraph2)
ConnectionGraph2();
~ConnectionGraph2();
/// \brief Given a remote system identified by RakNetGUID, return the list of SystemAddresses and RakNetGUIDs they are connected to
/// \param[in] remoteSystemGuid Which system we are referring to. This only works for remote systems, not ourselves.
/// \param[out] saOut A preallocated array to hold the output list of SystemAddress. Can be 0 if you don't care.
/// \param[out] guidOut A preallocated array to hold the output list of RakNetGUID. Can be 0 if you don't care.
/// \param[in,out] outLength On input, the size of \a saOut and \a guidOut. On output, modified to reflect the number of elements actually written
/// \return True if \a remoteSystemGuid was found. Otherwise false, and \a saOut, \a guidOut remain unchanged. \a outLength will be set to 0.
bool GetConnectionListForRemoteSystem(RakNetGUID remoteSystemGuid, SystemAddress *saOut, RakNetGUID *guidOut, unsigned int *outLength);
/// Returns if g1 is connected to g2
bool ConnectionExists(RakNetGUID g1, RakNetGUID g2);
/// Returns the average ping between two systems in the connection graph. Returns -1 if no connection exists between those systems
uint16_t GetPingBetweenSystems(RakNetGUID g1, RakNetGUID g2) const;
/// Returns the system with the lowest average ping among all its connections.
/// If you need one system in the peer to peer group to relay data, have the FullyConnectedMesh2 host call this function after host migration, and use that system
RakNetGUID GetLowestAveragePingSystem(void) const;
/// \brief If called with false, then new connections are only added to the connection graph when you call ProcessNewConnection();
/// \details This is useful if you want to perform validation before connecting a system to a mesh, or if you want a submesh (for example a server cloud)
/// \param[in] b True to automatically call ProcessNewConnection() on any new connection, false to not do so. Defaults to true.
void SetAutoProcessNewConnections(bool b);
/// \brief Returns value passed to SetAutoProcessNewConnections()
/// \return Value passed to SetAutoProcessNewConnections(), or the default of true if it was never called
bool GetAutoProcessNewConnections(void) const;
/// \brief If you call SetAutoProcessNewConnections(false);, then you will need to manually call ProcessNewConnection() on new connections
/// \details On ID_NEW_INCOMING_CONNECTION or ID_CONNECTION_REQUEST_ACCEPTED, adds that system to the graph
/// Do not call ProcessNewConnection() manually otherwise
/// \param[in] The packet->SystemAddress member
/// \param[in] The packet->guid member
void AddParticipant(const SystemAddress &systemAddress, RakNetGUID rakNetGUID);
/// Get the participants added with AddParticipant()
/// \param[out] participantList Participants added with AddParticipant();
void GetParticipantList(DataStructures::OrderedList<RakNetGUID, RakNetGUID> &participantList);
/// \internal
struct SystemAddressAndGuid
{
SystemAddress systemAddress;
RakNetGUID guid;
uint16_t sendersPingToThatSystem;
};
/// \internal
static int SystemAddressAndGuidComp( const SystemAddressAndGuid &key, const SystemAddressAndGuid &data );
/// \internal
struct RemoteSystem
{
DataStructures::OrderedList<SystemAddressAndGuid,SystemAddressAndGuid,ConnectionGraph2::SystemAddressAndGuidComp> remoteConnections;
RakNetGUID guid;
};
/// \internal
static int RemoteSystemComp( const RakNetGUID &key, RemoteSystem * const &data );
protected:
/// \internal
virtual void OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason );
/// \internal
virtual void OnNewConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, bool isIncoming);
/// \internal
virtual PluginReceiveResult OnReceive(Packet *packet);
// List of systems I am connected to, which in turn stores which systems they are connected to
DataStructures::OrderedList<RakNetGUID, RemoteSystem*, ConnectionGraph2::RemoteSystemComp> remoteSystems;
bool autoProcessNewConnections;
};
} // namespace RakNet
#endif // #ifndef __CONNECTION_GRAPH_2_H
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,311 @@
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_ConsoleServer==1
#include "ConsoleServer.h"
#include "TransportInterface.h"
#include "CommandParserInterface.h"
#include <string.h>
#include <stdlib.h>
#define COMMAND_DELINATOR ' '
#define COMMAND_DELINATOR_TOGGLE '"'
#include "LinuxStrings.h"
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(ConsoleServer,ConsoleServer);
ConsoleServer::ConsoleServer()
{
transport=0;
password[0]=0;
prompt=0;
}
ConsoleServer::~ConsoleServer()
{
if (prompt)
rakFree_Ex(prompt, _FILE_AND_LINE_);
}
void ConsoleServer::SetTransportProvider(TransportInterface *transportInterface, unsigned short port)
{
// Replace the current TransportInterface, stopping the old one, if present, and starting the new one.
if (transportInterface)
{
if (transport)
{
RemoveCommandParser(transport->GetCommandParser());
transport->Stop();
}
transport=transportInterface;
transport->Start(port, true);
unsigned i;
for (i=0; i < commandParserList.Size(); i++)
commandParserList[i]->OnTransportChange(transport);
// The transport itself might have a command parser - for example password for the RakNet transport
AddCommandParser(transport->GetCommandParser());
}
}
void ConsoleServer::AddCommandParser(CommandParserInterface *commandParserInterface)
{
if (commandParserInterface==0)
return;
// Non-duplicate insertion
unsigned i;
for (i=0; i < commandParserList.Size(); i++)
{
if (commandParserList[i]==commandParserInterface)
return;
if (_stricmp(commandParserList[i]->GetName(), commandParserInterface->GetName())==0)
{
// Naming conflict between two command parsers
RakAssert(0);
return;
}
}
commandParserList.Insert(commandParserInterface, _FILE_AND_LINE_);
if (transport)
commandParserInterface->OnTransportChange(transport);
}
void ConsoleServer::RemoveCommandParser(CommandParserInterface *commandParserInterface)
{
if (commandParserInterface==0)
return;
// Overwrite the element we are removing from the back of the list and delete the back of the list
unsigned i;
for (i=0; i < commandParserList.Size(); i++)
{
if (commandParserList[i]==commandParserInterface)
{
commandParserList[i]=commandParserList[commandParserList.Size()-1];
commandParserList.RemoveFromEnd();
return;
}
}
}
void ConsoleServer::Update(void)
{
unsigned i;
char *parameterList[20]; // Up to 20 parameters
unsigned numParameters;
RakNet::SystemAddress newOrLostConnectionId;
RakNet::Packet *p;
RakNet::RegisteredCommand rc;
p = transport->Receive();
newOrLostConnectionId=transport->HasNewIncomingConnection();
if (newOrLostConnectionId!=UNASSIGNED_SYSTEM_ADDRESS)
{
for (i=0; i < commandParserList.Size(); i++)
{
commandParserList[i]->OnNewIncomingConnection(newOrLostConnectionId, transport);
}
transport->Send(newOrLostConnectionId, "Connected to remote command console.\r\nType 'help' for help.\r\n");
ListParsers(newOrLostConnectionId);
ShowPrompt(newOrLostConnectionId);
}
newOrLostConnectionId=transport->HasLostConnection();
if (newOrLostConnectionId!=UNASSIGNED_SYSTEM_ADDRESS)
{
for (i=0; i < commandParserList.Size(); i++)
commandParserList[i]->OnConnectionLost(newOrLostConnectionId, transport);
}
while (p)
{
bool commandParsed=false;
char copy[REMOTE_MAX_TEXT_INPUT];
memcpy(copy, p->data, p->length);
copy[p->length]=0;
RakNet::CommandParserInterface::ParseConsoleString((char*)p->data, COMMAND_DELINATOR, COMMAND_DELINATOR_TOGGLE, &numParameters, parameterList, 20); // Up to 20 parameters
if (numParameters==0)
{
transport->DeallocatePacket(p);
p = transport->Receive();
continue;
}
if (_stricmp(*parameterList, "help")==0 && numParameters<=2)
{
// Find the parser specified and display help for it
if (numParameters==1)
{
transport->Send(p->systemAddress, "\r\nINSTRUCTIONS:\r\n");
transport->Send(p->systemAddress, "Enter commands on your keyboard, using spaces to delineate parameters.\r\n");
transport->Send(p->systemAddress, "You can use quotation marks to toggle space delineation.\r\n");
transport->Send(p->systemAddress, "You can connect multiple times from the same computer.\r\n");
transport->Send(p->systemAddress, "You can direct commands to a parser by prefixing the parser name or number.\r\n");
transport->Send(p->systemAddress, "COMMANDS:\r\n");
transport->Send(p->systemAddress, "help Show this display.\r\n");
transport->Send(p->systemAddress, "help <ParserName> Show help on a particular parser.\r\n");
transport->Send(p->systemAddress, "help <CommandName> Show help on a particular command.\r\n");
transport->Send(p->systemAddress, "quit Disconnects from the server.\r\n");
transport->Send(p->systemAddress, "[<ParserName>] <Command> [<Parameters>] Execute a command\r\n");
transport->Send(p->systemAddress, "[<ParserNumber>] <Command> [<Parameters>] Execute a command\r\n");
ListParsers(p->systemAddress);
//ShowPrompt(p->systemAddress);
}
else // numParameters == 2, including the help tag
{
for (i=0; i < commandParserList.Size(); i++)
{
if (_stricmp(parameterList[1], commandParserList[i]->GetName())==0)
{
commandParsed=true;
commandParserList[i]->SendHelp(transport, p->systemAddress);
transport->Send(p->systemAddress, "COMMAND LIST:\r\n");
commandParserList[i]->SendCommandList(transport, p->systemAddress);
transport->Send(p->systemAddress, "\r\n");
break;
}
}
if (commandParsed==false)
{
// Try again, for all commands for all parsers.
RakNet::RegisteredCommand rc;
for (i=0; i < commandParserList.Size(); i++)
{
if (commandParserList[i]->GetRegisteredCommand(parameterList[1], &rc))
{
if (rc.parameterCount==RakNet::CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS)
transport->Send(p->systemAddress, "(Variable parms): %s %s\r\n", rc.command, rc.commandHelp);
else
transport->Send(p->systemAddress, "(%i parms): %s %s\r\n", rc.parameterCount, rc.command, rc.commandHelp);
commandParsed=true;
break;
}
}
}
if (commandParsed==false)
{
// Don't know what to do
transport->Send(p->systemAddress, "Unknown help topic: %s.\r\n", parameterList[1]);
}
//ShowPrompt(p->systemAddress);
}
}
else if (_stricmp(*parameterList, "quit")==0 && numParameters==1)
{
transport->Send(p->systemAddress, "Goodbye!\r\n");
transport->CloseConnection(p->systemAddress);
}
else
{
bool tryAllParsers=true;
bool failed=false;
if (numParameters >=2) // At minimum <CommandParserName> <Command>
{
unsigned commandParserIndex=(unsigned)-1;
// Prefixing with numbers directs to a particular parser
if (**parameterList>='0' && **parameterList<='9')
{
commandParserIndex=atoi(*parameterList); // Use specified parser unless it's an invalid number
commandParserIndex--; // Subtract 1 since we displayed numbers starting at index+1
if (commandParserIndex >= commandParserList.Size())
{
transport->Send(p->systemAddress, "Invalid index.\r\n");
failed=true;
}
}
else
{
// // Prefixing with the name of a command parser directs to that parser. See if the first word matches a parser
for (i=0; i < commandParserList.Size(); i++)
{
if (_stricmp(parameterList[0], commandParserList[i]->GetName())==0)
{
commandParserIndex=i; // Matches parser at index i
break;
}
}
}
if (failed==false)
{
// -1 means undirected, so otherwise this is directed to a target
if (commandParserIndex!=(unsigned)-1)
{
// Only this parser should use this command
tryAllParsers=false;
if (commandParserList[commandParserIndex]->GetRegisteredCommand(parameterList[1], &rc))
{
commandParsed=true;
if (rc.parameterCount==CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS || rc.parameterCount==numParameters-2)
commandParserList[commandParserIndex]->OnCommand(rc.command, numParameters-2, parameterList+2, transport, p->systemAddress, copy);
else
transport->Send(p->systemAddress, "Invalid parameter count.\r\n(%i parms): %s %s\r\n", rc.parameterCount, rc.command, rc.commandHelp);
}
}
}
}
if (failed == false && tryAllParsers)
{
for (i=0; i < commandParserList.Size(); i++)
{
// Undirected command. Try all the parsers to see if they understand the command
// Pass the 1nd element as the command, and the remainder as the parameter list
if (commandParserList[i]->GetRegisteredCommand(parameterList[0], &rc))
{
commandParsed=true;
if (rc.parameterCount==CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS || rc.parameterCount==numParameters-1)
commandParserList[i]->OnCommand(rc.command, numParameters-1, parameterList+1, transport, p->systemAddress, copy);
else
transport->Send(p->systemAddress, "Invalid parameter count.\r\n(%i parms): %s %s\r\n", rc.parameterCount, rc.command, rc.commandHelp);
}
}
}
if (commandParsed==false && commandParserList.Size() > 0)
{
transport->Send(p->systemAddress, "Unknown command: Type 'help' for help.\r\n");
}
}
ShowPrompt(p->systemAddress);
transport->DeallocatePacket(p);
p = transport->Receive();
}
}
void ConsoleServer::ListParsers(SystemAddress systemAddress)
{
transport->Send(systemAddress,"INSTALLED PARSERS:\r\n");
unsigned i;
for (i=0; i < commandParserList.Size(); i++)
{
transport->Send(systemAddress, "%i. %s\r\n", i+1, commandParserList[i]->GetName());
}
}
void ConsoleServer::ShowPrompt(SystemAddress systemAddress)
{
transport->Send(systemAddress, prompt);
}
void ConsoleServer::SetPrompt(const char *_prompt)
{
if (prompt)
rakFree_Ex(prompt,_FILE_AND_LINE_);
if (_prompt && _prompt[0])
{
size_t len = strlen(_prompt);
prompt = (char*) rakMalloc_Ex(len+1,_FILE_AND_LINE_);
strcpy(prompt,_prompt);
}
else
prompt=0;
}
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,77 @@
/// \file ConsoleServer.h
/// \brief Contains ConsoleServer , used to plugin to your game to accept remote console-based connections
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_ConsoleServer==1
#ifndef __CONSOLE_SERVER_H
#define __CONSOLE_SERVER_H
#include "RakMemoryOverride.h"
#include "DS_List.h"
#include "RakNetTypes.h"
#include "Export.h"
namespace RakNet
{
/// Forward declarations
class TransportInterface;
class CommandParserInterface;
/// \brief The main entry point for the server portion of your remote console application support.
/// \details ConsoleServer takes one TransportInterface and one or more CommandParserInterface (s)
/// The TransportInterface will be used to send data between the server and the client. The connecting client must support the
/// protocol used by your derivation of TransportInterface . TelnetTransport and RakNetTransport are two such derivations .
/// When a command is sent by a remote console, it will be processed by your implementations of CommandParserInterface
class RAK_DLL_EXPORT ConsoleServer
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(ConsoleServer)
ConsoleServer();
~ConsoleServer();
/// \brief Call this with a derivation of TransportInterface so that the console server can send and receive commands
/// \param[in] transportInterface Your interface to use.
/// \param[in] port The port to host on. Telnet uses port 23 by default. RakNet can use whatever you want.
void SetTransportProvider(TransportInterface *transportInterface, unsigned short port);
/// \brief Add an implementation of CommandParserInterface to the list of command parsers.
/// \param[in] commandParserInterface The command parser referred to
void AddCommandParser(CommandParserInterface *commandParserInterface);
/// \brief Remove an implementation of CommandParserInterface previously added with AddCommandParser().
/// \param[in] commandParserInterface The command parser referred to
void RemoveCommandParser(CommandParserInterface *commandParserInterface);
/// \brief Call update to read packet sent from your TransportInterface.
/// You should do this fairly frequently.
void Update(void);
/// \brief Sets a prompt to show when waiting for user input.
/// \details Pass an empty string to clear the prompt
/// Defaults to no prompt
/// \param[in] _prompt Null-terminated string of the prompt to use. If you want a newline, be sure to use /r/n
void SetPrompt(const char *_prompt);
protected:
void ListParsers(SystemAddress systemAddress);
void ShowPrompt(SystemAddress systemAddress);
TransportInterface *transport;
DataStructures::List<CommandParserInterface *> commandParserList;
char* password[256];
char *prompt;
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,149 @@
#include "DS_BytePool.h"
#include "RakAssert.h"
#ifndef __APPLE__
// Use stdlib and not malloc for compatibility
#include <stdlib.h>
#endif
using namespace DataStructures;
BytePool::BytePool()
{
pool128.SetPageSize(8192*4);
pool512.SetPageSize(8192*4);
pool2048.SetPageSize(8192*4);
pool8192.SetPageSize(8192*4);
}
BytePool::~BytePool()
{
}
void BytePool::SetPageSize(int size)
{
pool128.SetPageSize(size);
pool512.SetPageSize(size);
pool2048.SetPageSize(size);
pool8192.SetPageSize(size);
}
unsigned char *BytePool::Allocate(int bytesWanted, const char *file, unsigned int line)
{
#ifdef _DISABLE_BYTE_POOL
return rakMalloc_Ex(bytesWanted, _FILE_AND_LINE_);
#endif
unsigned char *out;
if (bytesWanted <= 127)
{
#ifdef _THREADSAFE_BYTE_POOL
mutex128.Lock();
#endif
out = (unsigned char*) pool128.Allocate(file, line);
#ifdef _THREADSAFE_BYTE_POOL
mutex128.Unlock();
#endif
out[0]=0;
return ((unsigned char*) out)+1;
}
if (bytesWanted <= 511)
{
#ifdef _THREADSAFE_BYTE_POOL
mutex512.Lock();
#endif
out = (unsigned char*) pool512.Allocate(file, line);
#ifdef _THREADSAFE_BYTE_POOL
mutex512.Unlock();
#endif
out[0]=1;
return ((unsigned char*) out)+1;
}
if (bytesWanted <= 2047)
{
#ifdef _THREADSAFE_BYTE_POOL
mutex2048.Lock();
#endif
out = (unsigned char*) pool2048.Allocate(file, line);
#ifdef _THREADSAFE_BYTE_POOL
mutex2048.Unlock();
#endif
out[0]=2;
return ((unsigned char*) out)+1;
}
if (bytesWanted <= 8191)
{
#ifdef _THREADSAFE_BYTE_POOL
mutex8192.Lock();
#endif
out = (unsigned char*) pool8192.Allocate(file, line);
#ifdef _THREADSAFE_BYTE_POOL
mutex8192.Unlock();
#endif
out[0]=3;
return ((unsigned char*) out)+1;
}
out = (unsigned char*) rakMalloc_Ex(bytesWanted+1, _FILE_AND_LINE_);
out[0]=(unsigned char)255;
return out+1;
}
void BytePool::Release(unsigned char *data, const char *file, unsigned int line)
{
#ifdef _DISABLE_BYTE_POOL
_rakFree_Ex(data, _FILE_AND_LINE_ );
#endif
unsigned char *realData = data-1;
switch (realData[0])
{
case 0:
#ifdef _THREADSAFE_BYTE_POOL
mutex128.Lock();
#endif
pool128.Release((unsigned char(*)[128]) realData, file, line );
#ifdef _THREADSAFE_BYTE_POOL
mutex128.Unlock();
#endif
break;
case 1:
#ifdef _THREADSAFE_BYTE_POOL
mutex512.Lock();
#endif
pool512.Release((unsigned char(*)[512]) realData, file, line );
#ifdef _THREADSAFE_BYTE_POOL
mutex512.Unlock();
#endif
break;
case 2:
#ifdef _THREADSAFE_BYTE_POOL
mutex2048.Lock();
#endif
pool2048.Release((unsigned char(*)[2048]) realData, file, line );
#ifdef _THREADSAFE_BYTE_POOL
mutex2048.Unlock();
#endif
break;
case 3:
#ifdef _THREADSAFE_BYTE_POOL
mutex8192.Lock();
#endif
pool8192.Release((unsigned char(*)[8192]) realData, file, line );
#ifdef _THREADSAFE_BYTE_POOL
mutex8192.Unlock();
#endif
break;
case 255:
rakFree_Ex(realData, file, line );
break;
default:
RakAssert(0);
break;
}
}
void BytePool::Clear(const char *file, unsigned int line)
{
(void) file;
(void) line;
#ifdef _THREADSAFE_BYTE_POOL
pool128.Clear(file, line);
pool512.Clear(file, line);
pool2048.Clear(file, line);
pool8192.Clear(file, line);
#endif
}

View File

@@ -0,0 +1,46 @@
/// \file DS_BytePool.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __BYTE_POOL_H
#define __BYTE_POOL_H
#include "RakMemoryOverride.h"
#include "DS_MemoryPool.h"
#include "Export.h"
#include "SimpleMutex.h"
#include "RakAssert.h"
// #define _DISABLE_BYTE_POOL
// #define _THREADSAFE_BYTE_POOL
namespace DataStructures
{
// Allocate some number of bytes from pools. Uses the heap if necessary.
class RAK_DLL_EXPORT BytePool
{
public:
BytePool();
~BytePool();
// Should be at least 8 times bigger than 8192
void SetPageSize(int size);
unsigned char* Allocate(int bytesWanted, const char *file, unsigned int line);
void Release(unsigned char *data, const char *file, unsigned int line);
void Clear(const char *file, unsigned int line);
protected:
MemoryPool<unsigned char[128]> pool128;
MemoryPool<unsigned char[512]> pool512;
MemoryPool<unsigned char[2048]> pool2048;
MemoryPool<unsigned char[8192]> pool8192;
#ifdef _THREADSAFE_BYTE_POOL
SimpleMutex mutex128;
SimpleMutex mutex512;
SimpleMutex mutex2048;
SimpleMutex mutex8192;
#endif
};
}
#endif

View File

@@ -0,0 +1,127 @@
#include "DS_ByteQueue.h"
#include <string.h> // Memmove
#include <stdlib.h> // realloc
#include <stdio.h>
using namespace DataStructures;
ByteQueue::ByteQueue()
{
readOffset=writeOffset=lengthAllocated=0;
data=0;
}
ByteQueue::~ByteQueue()
{
Clear(_FILE_AND_LINE_);
}
void ByteQueue::WriteBytes(const char *in, unsigned length, const char *file, unsigned int line)
{
unsigned bytesWritten;
bytesWritten=GetBytesWritten();
if (lengthAllocated==0 || length > lengthAllocated-bytesWritten-1)
{
unsigned oldLengthAllocated=lengthAllocated;
// Always need to waste 1 byte for the math to work, else writeoffset==readoffset
unsigned newAmountToAllocate=length+oldLengthAllocated+1;
if (newAmountToAllocate<256)
newAmountToAllocate=256;
lengthAllocated=lengthAllocated + newAmountToAllocate;
data=(char*)rakRealloc_Ex(data, lengthAllocated, file, line);
if (writeOffset < readOffset)
{
if (writeOffset <= newAmountToAllocate)
{
memcpy(data + oldLengthAllocated, data, writeOffset);
writeOffset=readOffset+bytesWritten;
}
else
{
memcpy(data + oldLengthAllocated, data, newAmountToAllocate);
memmove(data, data+newAmountToAllocate, writeOffset-newAmountToAllocate);
writeOffset-=newAmountToAllocate;
}
}
}
if (length <= lengthAllocated-writeOffset)
memcpy(data+writeOffset, in, length);
else
{
// Wrap
memcpy(data+writeOffset, in, lengthAllocated-writeOffset);
memcpy(data, in+(lengthAllocated-writeOffset), length-(lengthAllocated-writeOffset));
}
writeOffset=(writeOffset+length) % lengthAllocated;
}
bool ByteQueue::ReadBytes(char *out, unsigned maxLengthToRead, bool peek)
{
unsigned bytesWritten = GetBytesWritten();
unsigned bytesToRead = bytesWritten < maxLengthToRead ? bytesWritten : maxLengthToRead;
if (bytesToRead==0)
return false;
if (writeOffset>=readOffset)
{
memcpy(out, data+readOffset, bytesToRead);
}
else
{
unsigned availableUntilWrap = lengthAllocated-readOffset;
if (bytesToRead <= availableUntilWrap)
{
memcpy(out, data+readOffset, bytesToRead);
}
else
{
memcpy(out, data+readOffset, availableUntilWrap);
memcpy(out+availableUntilWrap, data, bytesToRead-availableUntilWrap);
}
}
if (peek==false)
IncrementReadOffset(bytesToRead);
return true;
}
char* ByteQueue::PeekContiguousBytes(unsigned int *outLength) const
{
if (writeOffset>=readOffset)
*outLength=writeOffset-readOffset;
else
*outLength=lengthAllocated-readOffset;
return data+readOffset;
}
void ByteQueue::Clear(const char *file, unsigned int line)
{
if (lengthAllocated)
rakFree_Ex(data, file, line );
readOffset=writeOffset=lengthAllocated=0;
data=0;
}
unsigned ByteQueue::GetBytesWritten(void) const
{
if (writeOffset>=readOffset)
return writeOffset-readOffset;
else
return writeOffset+(lengthAllocated-readOffset);
}
void ByteQueue::IncrementReadOffset(unsigned length)
{
readOffset=(readOffset+length) % lengthAllocated;
}
void ByteQueue::DecrementReadOffset(unsigned length)
{
if (length>readOffset)
readOffset=lengthAllocated-(length-readOffset);
else
readOffset-=length;
}
void ByteQueue::Print(void)
{
unsigned i;
for (i=readOffset; i!=writeOffset; i++)
RAKNET_DEBUG_PRINTF("%i ", data[i]);
RAKNET_DEBUG_PRINTF("\n");
}

View File

@@ -0,0 +1,40 @@
/// \file DS_ByteQueue.h
/// \internal
/// \brief Byte queue
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __BYTE_QUEUE_H
#define __BYTE_QUEUE_H
#include "RakMemoryOverride.h"
#include "Export.h"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
class ByteQueue
{
public:
ByteQueue();
~ByteQueue();
void WriteBytes(const char *in, unsigned length, const char *file, unsigned int line);
bool ReadBytes(char *out, unsigned maxLengthToRead, bool peek);
unsigned GetBytesWritten(void) const;
char* PeekContiguousBytes(unsigned int *outLength) const;
void IncrementReadOffset(unsigned length);
void DecrementReadOffset(unsigned length);
void Clear(const char *file, unsigned int line);
void Print(void);
protected:
char *data;
unsigned readOffset, writeOffset, lengthAllocated;
};
}
#endif

View File

@@ -0,0 +1,344 @@
/// \internal
/// \brief Hashing container
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __HASH_H
#define __HASH_H
#include "RakAssert.h"
#include <string.h> // memmove
#include "Export.h"
#include "RakMemoryOverride.h"
#include "RakString.h"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
struct HashIndex
{
unsigned int primaryIndex;
unsigned int secondaryIndex;
bool IsInvalid(void) const {return primaryIndex==(unsigned int) -1;}
void SetInvalid(void) {primaryIndex=(unsigned int) -1; secondaryIndex=(unsigned int) -1;}
};
/// \brief Using a string as a identifier for a node, store an allocated pointer to that node
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
class RAK_DLL_EXPORT Hash
{
public:
/// Default constructor
Hash();
// Destructor
~Hash();
void Push(key_type key, const data_type &input, const char *file, unsigned int line );
data_type* Peek(key_type key );
bool Pop(data_type& out, key_type key, const char *file, unsigned int line );
bool RemoveAtIndex(HashIndex index, const char *file, unsigned int line );
bool Remove(key_type key, const char *file, unsigned int line );
HashIndex GetIndexOf(key_type key);
bool HasData(key_type key);
data_type& ItemAtIndex(const HashIndex &index);
key_type KeyAtIndex(const HashIndex &index);
void GetAsList(DataStructures::List<data_type> &itemList,DataStructures::List<key_type > &keyList,const char *file, unsigned int line) const;
unsigned int Size(void) const;
/// \brief Clear the list
void Clear( const char *file, unsigned int line );
struct Node
{
Node(key_type strIn, const data_type &_data) {string=strIn; data=_data;}
key_type string;
data_type data;
// Next in the list for this key
Node *next;
};
protected:
void ClearIndex(unsigned int index,const char *file, unsigned int line);
Node **nodeList;
unsigned int size;
};
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
Hash<key_type, data_type, HASH_SIZE, hashFunction>::Hash()
{
nodeList=0;
size=0;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
Hash<key_type, data_type, HASH_SIZE, hashFunction>::~Hash()
{
Clear(_FILE_AND_LINE_);
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
void Hash<key_type, data_type, HASH_SIZE, hashFunction>::Push(key_type key, const data_type &input, const char *file, unsigned int line )
{
unsigned long hashIndex = (*hashFunction)(key) % HASH_SIZE;
if (nodeList==0)
{
nodeList=RakNet::OP_NEW_ARRAY<Node *>(HASH_SIZE,file,line);
memset(nodeList,0,sizeof(Node *)*HASH_SIZE);
}
Node *newNode=RakNet::OP_NEW_2<Node>(file,line,key,input);
newNode->next=nodeList[hashIndex];
nodeList[hashIndex]=newNode;
size++;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
data_type* Hash<key_type, data_type, HASH_SIZE, hashFunction>::Peek(key_type key )
{
unsigned long hashIndex = (*hashFunction)(key) % HASH_SIZE;
Node *node = nodeList[hashIndex];
while (node!=0)
{
if (node->string==key)
return &node->data;
node=node->next;
}
return 0;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
bool Hash<key_type, data_type, HASH_SIZE, hashFunction>::Pop(data_type& out, key_type key, const char *file, unsigned int line )
{
unsigned long hashIndex = (*hashFunction)(key) % HASH_SIZE;
Node *node = nodeList[hashIndex];
if (node==0)
return false;
if (node->next==0)
{
// Only one item.
if (node->string==key)
{
// Delete last item
out=node->data;
ClearIndex(hashIndex,_FILE_AND_LINE_);
return true;
}
else
{
// Single item doesn't match
return false;
}
}
else if (node->string==key)
{
// First item does match, but more than one item
out=node->data;
nodeList[hashIndex]=node->next;
RakNet::OP_DELETE(node,file,line);
size--;
return true;
}
Node *last=node;
node=node->next;
while (node!=0)
{
// First item does not match, but subsequent item might
if (node->string==key)
{
out=node->data;
// Skip over subsequent item
last->next=node->next;
// Delete existing item
RakNet::OP_DELETE(node,file,line);
size--;
return true;
}
last=node;
node=node->next;
}
return false;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
bool Hash<key_type, data_type, HASH_SIZE, hashFunction>::RemoveAtIndex(HashIndex index, const char *file, unsigned int line )
{
if (index.IsInvalid())
return false;
Node *node = nodeList[index.primaryIndex];
if (node==0)
return false;
if (node->next==0)
{
// Delete last item
ClearIndex(index.primaryIndex,file,line);
return true;
}
else if (index.secondaryIndex==0)
{
// First item does match, but more than one item
nodeList[index.primaryIndex]=node->next;
RakNet::OP_DELETE(node,file,line);
size--;
return true;
}
Node *last=node;
node=node->next;
--index.secondaryIndex;
while (index.secondaryIndex!=0)
{
last=node;
node=node->next;
--index.secondaryIndex;
}
// Skip over subsequent item
last->next=node->next;
// Delete existing item
RakNet::OP_DELETE(node,file,line);
size--;
return true;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
bool Hash<key_type, data_type, HASH_SIZE, hashFunction>::Remove(key_type key, const char *file, unsigned int line )
{
return RemoveAtIndex(GetIndexOf(key),file,line);
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
HashIndex Hash<key_type, data_type, HASH_SIZE, hashFunction>::GetIndexOf(key_type key)
{
if (nodeList==0)
{
HashIndex temp;
temp.SetInvalid();
return temp;
}
HashIndex idx;
idx.primaryIndex=(*hashFunction)(key) % HASH_SIZE;
Node *node = nodeList[idx.primaryIndex];
if (node==0)
{
idx.SetInvalid();
return idx;
}
idx.secondaryIndex=0;
while (node!=0)
{
if (node->string==key)
{
return idx;
}
node=node->next;
idx.secondaryIndex++;
}
idx.SetInvalid();
return idx;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
bool Hash<key_type, data_type, HASH_SIZE, hashFunction>::HasData(key_type key)
{
return GetIndexOf(key).IsInvalid()==false;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
data_type& Hash<key_type, data_type, HASH_SIZE, hashFunction>::ItemAtIndex(const HashIndex &index)
{
Node *node = nodeList[index.primaryIndex];
RakAssert(node);
unsigned int i;
for (i=0; i < index.secondaryIndex; i++)
{
node=node->next;
RakAssert(node);
}
return node->data;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
key_type Hash<key_type, data_type, HASH_SIZE, hashFunction>::KeyAtIndex(const HashIndex &index)
{
Node *node = nodeList[index.primaryIndex];
RakAssert(node);
unsigned int i;
for (i=0; i < index.secondaryIndex; i++)
{
node=node->next;
RakAssert(node);
}
return node->string;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
void Hash<key_type, data_type, HASH_SIZE, hashFunction>::Clear(const char *file, unsigned int line)
{
if (nodeList)
{
unsigned int i;
for (i=0; i < HASH_SIZE; i++)
ClearIndex(i,file,line);
RakNet::OP_DELETE_ARRAY(nodeList,file,line);
nodeList=0;
size=0;
}
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
void Hash<key_type, data_type, HASH_SIZE, hashFunction>::ClearIndex(unsigned int index,const char *file, unsigned int line)
{
Node *node = nodeList[index];
Node *next;
while (node)
{
next=node->next;
RakNet::OP_DELETE(node,file,line);
node=next;
size--;
}
nodeList[index]=0;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
void Hash<key_type, data_type, HASH_SIZE, hashFunction>::GetAsList(DataStructures::List<data_type> &itemList,DataStructures::List<key_type > &keyList,const char *file, unsigned int line) const
{
if (nodeList==0)
return;
itemList.Clear(false,_FILE_AND_LINE_);
keyList.Clear(false,_FILE_AND_LINE_);
Node *node;
unsigned int i;
for (i=0; i < HASH_SIZE; i++)
{
if (nodeList[i])
{
node=nodeList[i];
while (node)
{
itemList.Push(node->data,file,line);
keyList.Push(node->string,file,line);
node=node->next;
}
}
}
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
unsigned int Hash<key_type, data_type, HASH_SIZE, hashFunction>::Size(void) const
{
return size;
}
}
#endif

View File

@@ -0,0 +1,297 @@
/// \file DS_Heap.h
/// \internal
/// \brief Heap (Also serves as a priority queue)
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __RAKNET_HEAP_H
#define __RAKNET_HEAP_H
#include "RakMemoryOverride.h"
#include "DS_List.h"
#include "Export.h"
#include "RakAssert.h"
#ifdef _MSC_VER
#pragma warning( push )
#endif
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
template <class weight_type, class data_type, bool isMaxHeap>
class RAK_DLL_EXPORT Heap
{
public:
struct HeapNode
{
HeapNode() {}
HeapNode(const weight_type &w, const data_type &d) : weight(w), data(d) {}
weight_type weight; // I'm assuming key is a native numerical type - float or int
data_type data;
};
Heap();
~Heap();
void Push(const weight_type &weight, const data_type &data, const char *file, unsigned int line);
/// Call before calling PushSeries, for a new series of items
void StartSeries(void) {optimizeNextSeriesPush=false;}
/// If you are going to push a list of items, where the weights of the items on the list are in order and follow the heap order, PushSeries is faster than Push()
void PushSeries(const weight_type &weight, const data_type &data, const char *file, unsigned int line);
data_type Pop(const unsigned startingIndex);
data_type Peek(const unsigned startingIndex=0) const;
weight_type PeekWeight(const unsigned startingIndex=0) const;
void Clear(bool doNotDeallocateSmallBlocks, const char *file, unsigned int line);
data_type& operator[] ( const unsigned int position ) const;
unsigned Size(void) const;
protected:
unsigned LeftChild(const unsigned i) const;
unsigned RightChild(const unsigned i) const;
unsigned Parent(const unsigned i) const;
void Swap(const unsigned i, const unsigned j);
DataStructures::List<HeapNode> heap;
bool optimizeNextSeriesPush;
};
template <class weight_type, class data_type, bool isMaxHeap>
Heap<weight_type, data_type, isMaxHeap>::Heap()
{
optimizeNextSeriesPush=false;
}
template <class weight_type, class data_type, bool isMaxHeap>
Heap<weight_type, data_type, isMaxHeap>::~Heap()
{
//Clear(true, _FILE_AND_LINE_);
}
template <class weight_type, class data_type, bool isMaxHeap>
void Heap<weight_type, data_type, isMaxHeap>::PushSeries(const weight_type &weight, const data_type &data, const char *file, unsigned int line)
{
if (optimizeNextSeriesPush==false)
{
// If the weight of what we are inserting is greater than / less than in order of the heap of every sibling and sibling of parent, then can optimize next push
unsigned currentIndex = heap.Size();
unsigned parentIndex;
if (currentIndex>0)
{
for (parentIndex = Parent(currentIndex); parentIndex < currentIndex; parentIndex++)
{
#ifdef _MSC_VER
#pragma warning(disable:4127) // conditional expression is constant
#endif
if (isMaxHeap)
{
// Every child is less than its parent
if (weight>heap[parentIndex].weight)
{
// Can't optimize
Push(weight,data,file,line);
return;
}
}
else
{
// Every child is greater than than its parent
if (weight<heap[parentIndex].weight)
{
// Can't optimize
Push(weight,data,file,line);
return;
}
}
}
}
// Parent's subsequent siblings and this row's siblings all are less than / greater than inserted element. Can insert all further elements straight to the end
heap.Insert(HeapNode(weight, data), file, line);
optimizeNextSeriesPush=true;
}
else
{
heap.Insert(HeapNode(weight, data), file, line);
}
}
template <class weight_type, class data_type, bool isMaxHeap>
void Heap<weight_type, data_type, isMaxHeap>::Push(const weight_type &weight, const data_type &data, const char *file, unsigned int line)
{
unsigned currentIndex = heap.Size();
unsigned parentIndex;
heap.Insert(HeapNode(weight, data), file, line);
while (currentIndex!=0)
{
parentIndex = Parent(currentIndex);
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
if (isMaxHeap)
{
if (heap[parentIndex].weight < weight)
{
Swap(currentIndex, parentIndex);
currentIndex=parentIndex;
}
else
break;
}
else
{
if (heap[parentIndex].weight > weight)
{
Swap(currentIndex, parentIndex);
currentIndex=parentIndex;
}
else
break;
}
}
}
template <class weight_type, class data_type, bool isMaxHeap>
data_type Heap<weight_type, data_type, isMaxHeap>::Pop(const unsigned startingIndex)
{
// While we have children, swap out with the larger of the two children.
// This line will assert on an empty heap
data_type returnValue=heap[startingIndex].data;
// Move the last element to the head, and re-heapify
heap[startingIndex]=heap[heap.Size()-1];
unsigned currentIndex,leftChild,rightChild;
weight_type currentWeight;
currentIndex=startingIndex;
currentWeight=heap[startingIndex].weight;
heap.RemoveFromEnd();
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while (1)
{
leftChild=LeftChild(currentIndex);
rightChild=RightChild(currentIndex);
if (leftChild >= heap.Size())
{
// Done
return returnValue;
}
if (rightChild >= heap.Size())
{
// Only left node.
if ((isMaxHeap==true && currentWeight < heap[leftChild].weight) ||
(isMaxHeap==false && currentWeight > heap[leftChild].weight))
Swap(leftChild, currentIndex);
return returnValue;
}
else
{
// Swap with the bigger/smaller of the two children and continue
if (isMaxHeap)
{
if (heap[leftChild].weight <= currentWeight && heap[rightChild].weight <= currentWeight)
return returnValue;
if (heap[leftChild].weight > heap[rightChild].weight)
{
Swap(leftChild, currentIndex);
currentIndex=leftChild;
}
else
{
Swap(rightChild, currentIndex);
currentIndex=rightChild;
}
}
else
{
if (heap[leftChild].weight >= currentWeight && heap[rightChild].weight >= currentWeight)
return returnValue;
if (heap[leftChild].weight < heap[rightChild].weight)
{
Swap(leftChild, currentIndex);
currentIndex=leftChild;
}
else
{
Swap(rightChild, currentIndex);
currentIndex=rightChild;
}
}
}
}
}
template <class weight_type, class data_type, bool isMaxHeap>
inline data_type Heap<weight_type, data_type, isMaxHeap>::Peek(const unsigned startingIndex) const
{
return heap[startingIndex].data;
}
template <class weight_type, class data_type, bool isMaxHeap>
inline weight_type Heap<weight_type, data_type, isMaxHeap>::PeekWeight(const unsigned startingIndex) const
{
return heap[startingIndex].weight;
}
template <class weight_type, class data_type, bool isMaxHeap>
void Heap<weight_type, data_type, isMaxHeap>::Clear(bool doNotDeallocateSmallBlocks, const char *file, unsigned int line)
{
heap.Clear(doNotDeallocateSmallBlocks, file, line);
}
template <class weight_type, class data_type, bool isMaxHeap>
inline data_type& Heap<weight_type, data_type, isMaxHeap>::operator[] ( const unsigned int position ) const
{
return heap[position].data;
}
template <class weight_type, class data_type, bool isMaxHeap>
unsigned Heap<weight_type, data_type, isMaxHeap>::Size(void) const
{
return heap.Size();
}
template <class weight_type, class data_type, bool isMaxHeap>
inline unsigned Heap<weight_type, data_type, isMaxHeap>::LeftChild(const unsigned i) const
{
return i*2+1;
}
template <class weight_type, class data_type, bool isMaxHeap>
inline unsigned Heap<weight_type, data_type, isMaxHeap>::RightChild(const unsigned i) const
{
return i*2+2;
}
template <class weight_type, class data_type, bool isMaxHeap>
inline unsigned Heap<weight_type, data_type, isMaxHeap>::Parent(const unsigned i) const
{
#ifdef _DEBUG
RakAssert(i!=0);
#endif
return (i-1)/2;
}
template <class weight_type, class data_type, bool isMaxHeap>
void Heap<weight_type, data_type, isMaxHeap>::Swap(const unsigned i, const unsigned j)
{
HeapNode temp;
temp=heap[i];
heap[i]=heap[j];
heap[j]=temp;
}
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif

View File

@@ -0,0 +1,299 @@
/// \file
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "DS_HuffmanEncodingTree.h"
#include "DS_Queue.h"
#include "BitStream.h"
#include "RakAssert.h"
#ifdef _MSC_VER
#pragma warning( push )
#endif
using namespace RakNet;
HuffmanEncodingTree::HuffmanEncodingTree()
{
root = 0;
}
HuffmanEncodingTree::~HuffmanEncodingTree()
{
FreeMemory();
}
void HuffmanEncodingTree::FreeMemory( void )
{
if ( root == 0 )
return ;
// Use an in-order traversal to delete the tree
DataStructures::Queue<HuffmanEncodingTreeNode *> nodeQueue;
HuffmanEncodingTreeNode *node;
nodeQueue.Push( root, _FILE_AND_LINE_ );
while ( nodeQueue.Size() > 0 )
{
node = nodeQueue.Pop();
if ( node->left )
nodeQueue.Push( node->left, _FILE_AND_LINE_ );
if ( node->right )
nodeQueue.Push( node->right, _FILE_AND_LINE_ );
RakNet::OP_DELETE(node, _FILE_AND_LINE_);
}
// Delete the encoding table
for ( int i = 0; i < 256; i++ )
rakFree_Ex(encodingTable[ i ].encoding, _FILE_AND_LINE_ );
root = 0;
}
////#include <stdio.h>
// Given a frequency table of 256 elements, all with a frequency of 1 or more, generate the tree
void HuffmanEncodingTree::GenerateFromFrequencyTable( unsigned int frequencyTable[ 256 ] )
{
int counter;
HuffmanEncodingTreeNode * node;
HuffmanEncodingTreeNode *leafList[ 256 ]; // Keep a copy of the pointers to all the leaves so we can generate the encryption table bottom-up, which is easier
// 1. Make 256 trees each with a weight equal to the frequency of the corresponding character
DataStructures::LinkedList<HuffmanEncodingTreeNode *> huffmanEncodingTreeNodeList;
FreeMemory();
for ( counter = 0; counter < 256; counter++ )
{
node = RakNet::OP_NEW<HuffmanEncodingTreeNode>( _FILE_AND_LINE_ );
node->left = 0;
node->right = 0;
node->value = (unsigned char) counter;
node->weight = frequencyTable[ counter ];
if ( node->weight == 0 )
node->weight = 1; // 0 weights are illegal
leafList[ counter ] = node; // Used later to generate the encryption table
InsertNodeIntoSortedList( node, &huffmanEncodingTreeNodeList ); // Insert and maintain sort order.
}
// 2. While there is more than one tree, take the two smallest trees and merge them so that the two trees are the left and right
// children of a new node, where the new node has the weight the sum of the weight of the left and right child nodes.
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while ( 1 )
{
huffmanEncodingTreeNodeList.Beginning();
HuffmanEncodingTreeNode *lesser, *greater;
lesser = huffmanEncodingTreeNodeList.Pop();
greater = huffmanEncodingTreeNodeList.Pop();
node = RakNet::OP_NEW<HuffmanEncodingTreeNode>( _FILE_AND_LINE_ );
node->left = lesser;
node->right = greater;
node->weight = lesser->weight + greater->weight;
lesser->parent = node; // This is done to make generating the encryption table easier
greater->parent = node; // This is done to make generating the encryption table easier
if ( huffmanEncodingTreeNodeList.Size() == 0 )
{
// 3. Assign the one remaining node in the list to the root node.
root = node;
root->parent = 0;
break;
}
// Put the new node back into the list at the correct spot to maintain the sort. Linear search time
InsertNodeIntoSortedList( node, &huffmanEncodingTreeNodeList );
}
bool tempPath[ 256 ]; // Maximum path length is 256
unsigned short tempPathLength;
HuffmanEncodingTreeNode *currentNode;
RakNet::BitStream bitStream;
// Generate the encryption table. From before, we have an array of pointers to all the leaves which contain pointers to their parents.
// This can be done more efficiently but this isn't bad and it's way easier to program and debug
for ( counter = 0; counter < 256; counter++ )
{
// Already done at the end of the loop and before it!
tempPathLength = 0;
// Set the current node at the leaf
currentNode = leafList[ counter ];
do
{
if ( currentNode->parent->left == currentNode ) // We're storing the paths in reverse order.since we are going from the leaf to the root
tempPath[ tempPathLength++ ] = false;
else
tempPath[ tempPathLength++ ] = true;
currentNode = currentNode->parent;
}
while ( currentNode != root );
// Write to the bitstream in the reverse order that we stored the path, which gives us the correct order from the root to the leaf
while ( tempPathLength-- > 0 )
{
if ( tempPath[ tempPathLength ] ) // Write 1's and 0's because writing a bool will write the BitStream TYPE_CHECKING validation bits if that is defined along with the actual data bit, which is not what we want
bitStream.Write1();
else
bitStream.Write0();
}
// Read data from the bitstream, which is written to the encoding table in bits and bitlength. Note this function allocates the encodingTable[counter].encoding pointer
encodingTable[ counter ].bitLength = ( unsigned char ) bitStream.CopyData( &encodingTable[ counter ].encoding );
// Reset the bitstream for the next iteration
bitStream.Reset();
}
}
// Pass an array of bytes to array and a preallocated BitStream to receive the output
void HuffmanEncodingTree::EncodeArray( unsigned char *input, size_t sizeInBytes, RakNet::BitStream * output )
{
unsigned counter;
// For each input byte, Write out the corresponding series of 1's and 0's that give the encoded representation
for ( counter = 0; counter < sizeInBytes; counter++ )
{
output->WriteBits( encodingTable[ input[ counter ] ].encoding, encodingTable[ input[ counter ] ].bitLength, false ); // Data is left aligned
}
// Byte align the output so the unassigned remaining bits don't equate to some actual value
if ( output->GetNumberOfBitsUsed() % 8 != 0 )
{
// Find an input that is longer than the remaining bits. Write out part of it to pad the output to be byte aligned.
unsigned char remainingBits = (unsigned char) ( 8 - ( output->GetNumberOfBitsUsed() % 8 ) );
for ( counter = 0; counter < 256; counter++ )
if ( encodingTable[ counter ].bitLength > remainingBits )
{
output->WriteBits( encodingTable[ counter ].encoding, remainingBits, false ); // Data is left aligned
break;
}
#ifdef _DEBUG
RakAssert( counter != 256 ); // Given 256 elements, we should always be able to find an input that would be >= 7 bits
#endif
}
}
unsigned HuffmanEncodingTree::DecodeArray( RakNet::BitStream * input, BitSize_t sizeInBits, size_t maxCharsToWrite, unsigned char *output )
{
HuffmanEncodingTreeNode * currentNode;
unsigned outputWriteIndex;
outputWriteIndex = 0;
currentNode = root;
// For each bit, go left if it is a 0 and right if it is a 1. When we reach a leaf, that gives us the desired value and we restart from the root
for ( unsigned counter = 0; counter < sizeInBits; counter++ )
{
if ( input->ReadBit() == false ) // left!
currentNode = currentNode->left;
else
currentNode = currentNode->right;
if ( currentNode->left == 0 && currentNode->right == 0 ) // Leaf
{
if ( outputWriteIndex < maxCharsToWrite )
output[ outputWriteIndex ] = currentNode->value;
outputWriteIndex++;
currentNode = root;
}
}
return outputWriteIndex;
}
// Pass an array of encoded bytes to array and a preallocated BitStream to receive the output
void HuffmanEncodingTree::DecodeArray( unsigned char *input, BitSize_t sizeInBits, RakNet::BitStream * output )
{
HuffmanEncodingTreeNode * currentNode;
if ( sizeInBits <= 0 )
return ;
RakNet::BitStream bitStream( input, BITS_TO_BYTES(sizeInBits), false );
currentNode = root;
// For each bit, go left if it is a 0 and right if it is a 1. When we reach a leaf, that gives us the desired value and we restart from the root
for ( unsigned counter = 0; counter < sizeInBits; counter++ )
{
if ( bitStream.ReadBit() == false ) // left!
currentNode = currentNode->left;
else
currentNode = currentNode->right;
if ( currentNode->left == 0 && currentNode->right == 0 ) // Leaf
{
output->WriteBits( &( currentNode->value ), sizeof( char ) * 8, true ); // Use WriteBits instead of Write(char) because we want to avoid TYPE_CHECKING
currentNode = root;
}
}
}
// Insertion sort. Slow but easy to write in this case
void HuffmanEncodingTree::InsertNodeIntoSortedList( HuffmanEncodingTreeNode * node, DataStructures::LinkedList<HuffmanEncodingTreeNode *> *huffmanEncodingTreeNodeList ) const
{
if ( huffmanEncodingTreeNodeList->Size() == 0 )
{
huffmanEncodingTreeNodeList->Insert( node );
return ;
}
huffmanEncodingTreeNodeList->Beginning();
unsigned counter = 0;
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while ( 1 )
{
if ( huffmanEncodingTreeNodeList->Peek()->weight < node->weight )
++( *huffmanEncodingTreeNodeList );
else
{
huffmanEncodingTreeNodeList->Insert( node );
break;
}
// Didn't find a spot in the middle - add to the end
if ( ++counter == huffmanEncodingTreeNodeList->Size() )
{
huffmanEncodingTreeNodeList->End();
huffmanEncodingTreeNodeList->Add( node )
; // Add to the end
break;
}
}
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif

View File

@@ -0,0 +1,67 @@
/// \file DS_HuffmanEncodingTree.h
/// \brief \b [Internal] Generates a huffman encoding tree, used for string and global compression.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __HUFFMAN_ENCODING_TREE
#define __HUFFMAN_ENCODING_TREE
#include "RakMemoryOverride.h"
#include "DS_HuffmanEncodingTreeNode.h"
#include "BitStream.h"
#include "Export.h"
#include "DS_LinkedList.h"
namespace RakNet
{
/// This generates special cases of the huffman encoding tree using 8 bit keys with the additional condition that unused combinations of 8 bits are treated as a frequency of 1
class RAK_DLL_EXPORT HuffmanEncodingTree
{
public:
HuffmanEncodingTree();
~HuffmanEncodingTree();
/// \brief Pass an array of bytes to array and a preallocated BitStream to receive the output.
/// \param [in] input Array of bytes to encode
/// \param [in] sizeInBytes size of \a input
/// \param [out] output The bitstream to write to
void EncodeArray( unsigned char *input, size_t sizeInBytes, RakNet::BitStream * output );
// \brief Decodes an array encoded by EncodeArray().
unsigned DecodeArray( RakNet::BitStream * input, BitSize_t sizeInBits, size_t maxCharsToWrite, unsigned char *output );
void DecodeArray( unsigned char *input, BitSize_t sizeInBits, RakNet::BitStream * output );
/// \brief Given a frequency table of 256 elements, all with a frequency of 1 or more, generate the tree.
void GenerateFromFrequencyTable( unsigned int frequencyTable[ 256 ] );
/// \brief Free the memory used by the tree.
void FreeMemory( void );
private:
/// The root node of the tree
HuffmanEncodingTreeNode *root;
/// Used to hold bit encoding for one character
struct CharacterEncoding
{
unsigned char* encoding;
unsigned short bitLength;
};
CharacterEncoding encodingTable[ 256 ];
void InsertNodeIntoSortedList( HuffmanEncodingTreeNode * node, DataStructures::LinkedList<HuffmanEncodingTreeNode *> *huffmanEncodingTreeNodeList ) const;
};
} // namespace RakNet
#endif

View File

@@ -0,0 +1,57 @@
/// \file DS_HuffmanEncodingTreeFactory.h
/// \internal
/// \brief Creates instances of the class HuffmanEncodingTree
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __HUFFMAN_ENCODING_TREE_FACTORY
#define __HUFFMAN_ENCODING_TREE_FACTORY
#include "RakMemoryOverride.h"
namespace RakNet {
/// Forward declarations
class HuffmanEncodingTree;
/// \brief Creates instances of the class HuffmanEncodingTree
/// \details This class takes a frequency table and given that frequence table, will generate an instance of HuffmanEncodingTree
class HuffmanEncodingTreeFactory
{
public:
/// Default constructor
HuffmanEncodingTreeFactory();
/// \brief Reset the frequency table.
/// \details You don't need to call this unless you want to reuse the class for a new tree
void Reset( void );
/// \brief Pass an array of bytes to this to add those elements to the frequency table.
/// \param[in] array the data to insert into the frequency table
/// \param[in] size the size of the data to insert
void AddToFrequencyTable( unsigned char *array, int size );
/// \brief Copies the frequency table to the array passed. Retrieve the frequency table.
/// \param[in] _frequency The frequency table used currently
void GetFrequencyTable( unsigned int _frequency[ 256 ] );
/// \brief Returns the frequency table as a pointer.
/// \return the address of the frenquency table
unsigned int * GetFrequencyTable( void );
/// \brief Generate a HuffmanEncodingTree.
/// \details You can also use GetFrequencyTable and GenerateFromFrequencyTable in the tree itself
/// \return The generated instance of HuffmanEncodingTree
HuffmanEncodingTree * GenerateTree( void );
private:
/// Frequency table
unsigned int frequency[ 256 ];
};
} // namespace RakNet
#endif

View File

@@ -0,0 +1,21 @@
/// \file
/// \brief \b [Internal] A single node in the Huffman Encoding Tree.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __HUFFMAN_ENCODING_TREE_NODE
#define __HUFFMAN_ENCODING_TREE_NODE
struct HuffmanEncodingTreeNode
{
unsigned char value;
unsigned weight;
HuffmanEncodingTreeNode *left;
HuffmanEncodingTreeNode *right;
HuffmanEncodingTreeNode *parent;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,518 @@
/// \file DS_List.h
/// \internal
/// \brief Array based list.
/// \details Usually the Queue class is used instead, since it has all the same functionality and is only worse at random access.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __LIST_H
#define __LIST_H
#include "RakAssert.h"
#include <string.h> // memmove
#include "Export.h"
#include "RakMemoryOverride.h"
/// Maximum unsigned long
static const unsigned int MAX_UNSIGNED_LONG = 4294967295U;
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
/// \brief Array based implementation of a list.
/// \note ONLY USE THIS FOR SHALLOW COPIES. I don't bother with operator= to improve performance.
template <class list_type>
class RAK_DLL_EXPORT List
{
public:
/// Default constructor
List();
// Destructor
~List();
/// \brief Copy constructor.
/// \param[in] original_copy The list to duplicate
List( const List& original_copy );
/// \brief Assign one list to another.
List& operator= ( const List& original_copy );
/// \brief Access an element by its index in the array.
/// \param[in] position The index into the array.
/// \return The element at position \a position.
list_type& operator[] ( const unsigned int position ) const;
/// \brief Access an element by its index in the array.
/// \param[in] position The index into the array.
/// \return The element at position \a position.
list_type& Get ( const unsigned int position ) const;
/// \brief Push an element at the end of the stack.
/// \param[in] input The new element.
void Push(const list_type &input, const char *file, unsigned int line );
/// \brief Pop an element from the end of the stack.
/// \pre Size()>0
/// \return The element at the end.
list_type& Pop(void);
/// \brief Insert an element at position \a position in the list.
/// \param[in] input The new element.
/// \param[in] position The position of the new element.
void Insert( const list_type &input, const unsigned int position, const char *file, unsigned int line );
/// \brief Insert at the end of the list.
/// \param[in] input The new element.
void Insert( const list_type &input, const char *file, unsigned int line );
/// \brief Replace the value at \a position by \a input.
/// \details If the size of the list is less than @em position, it increase the capacity of
/// the list and fill slot with @em filler.
/// \param[in] input The element to replace at position @em position.
/// \param[in] filler The element use to fill new allocated capacity.
/// \param[in] position The position of input in the list.
void Replace( const list_type &input, const list_type filler, const unsigned int position, const char *file, unsigned int line );
/// \brief Replace the last element of the list by \a input.
/// \param[in] input The element used to replace the last element.
void Replace( const list_type &input );
/// \brief Delete the element at position \a position.
/// \param[in] position The index of the element to delete
void RemoveAtIndex( const unsigned int position );
/// \brief Delete the element at position \a position.
/// \note - swaps middle with end of list, only use if list order does not matter
/// \param[in] position The index of the element to delete
void RemoveAtIndexFast( const unsigned int position );
/// \brief Delete the element at the end of the list.
void RemoveFromEnd(const unsigned num=1);
/// \brief Returns the index of the specified item or MAX_UNSIGNED_LONG if not found.
/// \param[in] input The element to check for
/// \return The index or position of @em input in the list.
/// \retval MAX_UNSIGNED_LONG The object is not in the list
/// \retval [Integer] The index of the element in the list
unsigned int GetIndexOf( const list_type &input ) const;
/// \return The number of elements in the list
unsigned int Size( void ) const;
/// \brief Clear the list
void Clear( bool doNotDeallocateSmallBlocks, const char *file, unsigned int line );
/// \brief Preallocate the list, so it needs fewer reallocations at runtime.
void Preallocate( unsigned countNeeded, const char *file, unsigned int line );
/// \brief Frees overallocated members, to use the minimum memory necessary.
/// \attention
/// This is a slow operation
void Compress( const char *file, unsigned int line );
private:
/// An array of user values
list_type* listArray;
/// Number of elements in the list
unsigned int list_size;
/// Size of \a array
unsigned int allocation_size;
};
template <class list_type>
List<list_type>::List()
{
allocation_size = 0;
listArray = 0;
list_size = 0;
}
template <class list_type>
List<list_type>::~List()
{
if (allocation_size>0)
RakNet::OP_DELETE_ARRAY(listArray, _FILE_AND_LINE_);
}
template <class list_type>
List<list_type>::List( const List& original_copy )
{
// Allocate memory for copy
if ( original_copy.list_size == 0 )
{
list_size = 0;
allocation_size = 0;
}
else
{
listArray = RakNet::OP_NEW_ARRAY<list_type >( original_copy.list_size , _FILE_AND_LINE_ );
for ( unsigned int counter = 0; counter < original_copy.list_size; ++counter )
listArray[ counter ] = original_copy.listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(listArray, original_copy.listArray, original_copy.list_size*sizeof(list_type));
list_size = allocation_size = original_copy.list_size;
}
}
template <class list_type>
List<list_type>& List<list_type>::operator= ( const List& original_copy )
{
if ( ( &original_copy ) != this )
{
Clear( false, _FILE_AND_LINE_ );
// Allocate memory for copy
if ( original_copy.list_size == 0 )
{
list_size = 0;
allocation_size = 0;
}
else
{
listArray = RakNet::OP_NEW_ARRAY<list_type >( original_copy.list_size , _FILE_AND_LINE_ );
for ( unsigned int counter = 0; counter < original_copy.list_size; ++counter )
listArray[ counter ] = original_copy.listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(listArray, original_copy.listArray, original_copy.list_size*sizeof(list_type));
list_size = allocation_size = original_copy.list_size;
}
}
return *this;
}
template <class list_type>
inline list_type& List<list_type>::operator[] ( const unsigned int position ) const
{
#ifdef _DEBUG
if (position>=list_size)
{
RakAssert ( position < list_size );
}
#endif
return listArray[ position ];
}
// Just here for debugging
template <class list_type>
inline list_type& List<list_type>::Get ( const unsigned int position ) const
{
return listArray[ position ];
}
template <class list_type>
void List<list_type>::Push(const list_type &input, const char *file, unsigned int line)
{
Insert(input, file, line);
}
template <class list_type>
inline list_type& List<list_type>::Pop(void)
{
#ifdef _DEBUG
RakAssert(list_size>0);
#endif
--list_size;
return listArray[list_size];
}
template <class list_type>
void List<list_type>::Insert( const list_type &input, const unsigned int position, const char *file, unsigned int line )
{
#ifdef _DEBUG
if (position>list_size)
{
RakAssert( position <= list_size );
}
#endif
// Reallocate list if necessary
if ( list_size == allocation_size )
{
// allocate twice the currently allocated memory
list_type * new_array;
if ( allocation_size == 0 )
allocation_size = 16;
else
allocation_size *= 2;
new_array = RakNet::OP_NEW_ARRAY<list_type >( allocation_size , file, line );
// copy old array over
for ( unsigned int counter = 0; counter < list_size; ++counter )
new_array[ counter ] = listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(new_array, listArray, list_size*sizeof(list_type));
// set old array to point to the newly allocated and twice as large array
RakNet::OP_DELETE_ARRAY(listArray, file, line);
listArray = new_array;
}
// Move the elements in the list to make room
for ( unsigned int counter = list_size; counter != position; counter-- )
listArray[ counter ] = listArray[ counter - 1 ];
// Don't call constructors, assignment operators, etc.
//memmove(listArray+position+1, listArray+position, (list_size-position)*sizeof(list_type));
// Insert the new item at the correct spot
listArray[ position ] = input;
++list_size;
}
template <class list_type>
void List<list_type>::Insert( const list_type &input, const char *file, unsigned int line )
{
// Reallocate list if necessary
if ( list_size == allocation_size )
{
// allocate twice the currently allocated memory
list_type * new_array;
if ( allocation_size == 0 )
allocation_size = 16;
else
allocation_size *= 2;
new_array = RakNet::OP_NEW_ARRAY<list_type >( allocation_size , file, line );
if (listArray)
{
// copy old array over
for ( unsigned int counter = 0; counter < list_size; ++counter )
new_array[ counter ] = listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(new_array, listArray, list_size*sizeof(list_type));
// set old array to point to the newly allocated and twice as large array
RakNet::OP_DELETE_ARRAY(listArray, file, line);
}
listArray = new_array;
}
// Insert the new item at the correct spot
listArray[ list_size ] = input;
++list_size;
}
template <class list_type>
inline void List<list_type>::Replace( const list_type &input, const list_type filler, const unsigned int position, const char *file, unsigned int line )
{
if ( ( list_size > 0 ) && ( position < list_size ) )
{
// Direct replacement
listArray[ position ] = input;
}
else
{
if ( position >= allocation_size )
{
// Reallocate the list to size position and fill in blanks with filler
list_type * new_array;
allocation_size = position + 1;
new_array = RakNet::OP_NEW_ARRAY<list_type >( allocation_size , file, line );
// copy old array over
for ( unsigned int counter = 0; counter < list_size; ++counter )
new_array[ counter ] = listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(new_array, listArray, list_size*sizeof(list_type));
// set old array to point to the newly allocated array
RakNet::OP_DELETE_ARRAY(listArray, file, line);
listArray = new_array;
}
// Fill in holes with filler
while ( list_size < position )
listArray[ list_size++ ] = filler;
// Fill in the last element with the new item
listArray[ list_size++ ] = input;
#ifdef _DEBUG
RakAssert( list_size == position + 1 );
#endif
}
}
template <class list_type>
inline void List<list_type>::Replace( const list_type &input )
{
if ( list_size > 0 )
listArray[ list_size - 1 ] = input;
}
template <class list_type>
void List<list_type>::RemoveAtIndex( const unsigned int position )
{
#ifdef _DEBUG
if (position >= list_size)
{
RakAssert( position < list_size );
return;
}
#endif
if ( position < list_size )
{
// Compress the array
for ( unsigned int counter = position; counter < list_size - 1 ; ++counter )
listArray[ counter ] = listArray[ counter + 1 ];
// Don't call constructors, assignment operators, etc.
// memmove(listArray+position, listArray+position+1, (list_size-1-position) * sizeof(list_type));
RemoveFromEnd();
}
}
template <class list_type>
void List<list_type>::RemoveAtIndexFast( const unsigned int position )
{
#ifdef _DEBUG
if (position >= list_size)
{
RakAssert( position < list_size );
return;
}
#endif
--list_size;
listArray[position]=listArray[list_size];
}
template <class list_type>
inline void List<list_type>::RemoveFromEnd( const unsigned num )
{
// Delete the last elements on the list. No compression needed
#ifdef _DEBUG
RakAssert(list_size>=num);
#endif
list_size-=num;
}
template <class list_type>
unsigned int List<list_type>::GetIndexOf( const list_type &input ) const
{
for ( unsigned int i = 0; i < list_size; ++i )
if ( listArray[ i ] == input )
return i;
return MAX_UNSIGNED_LONG;
}
template <class list_type>
inline unsigned int List<list_type>::Size( void ) const
{
return list_size;
}
template <class list_type>
void List<list_type>::Clear( bool doNotDeallocateSmallBlocks, const char *file, unsigned int line )
{
if ( allocation_size == 0 )
return;
if (allocation_size>512 || doNotDeallocateSmallBlocks==false)
{
RakNet::OP_DELETE_ARRAY(listArray, file, line);
allocation_size = 0;
listArray = 0;
}
list_size = 0;
}
template <class list_type>
void List<list_type>::Compress( const char *file, unsigned int line )
{
list_type * new_array;
if ( allocation_size == 0 )
return ;
new_array = RakNet::OP_NEW_ARRAY<list_type >( allocation_size , file, line );
// copy old array over
for ( unsigned int counter = 0; counter < list_size; ++counter )
new_array[ counter ] = listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(new_array, listArray, list_size*sizeof(list_type));
// set old array to point to the newly allocated array
RakNet::OP_DELETE_ARRAY(listArray, file, line);
listArray = new_array;
}
template <class list_type>
void List<list_type>::Preallocate( unsigned countNeeded, const char *file, unsigned int line )
{
unsigned amountToAllocate = allocation_size;
if (allocation_size==0)
amountToAllocate=16;
while (amountToAllocate < countNeeded)
amountToAllocate<<=1;
if ( allocation_size < amountToAllocate)
{
// allocate twice the currently allocated memory
list_type * new_array;
allocation_size=amountToAllocate;
new_array = RakNet::OP_NEW_ARRAY< list_type >( allocation_size , file, line );
if (listArray)
{
// copy old array over
for ( unsigned int counter = 0; counter < list_size; ++counter )
new_array[ counter ] = listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(new_array, listArray, list_size*sizeof(list_type));
// set old array to point to the newly allocated and twice as large array
RakNet::OP_DELETE_ARRAY(listArray, file, line);
}
listArray = new_array;
}
}
} // End namespace
#endif

View File

@@ -0,0 +1,321 @@
/// \file DS_Map.h
/// \internal
/// \brief Map
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __RAKNET_MAP_H
#define __RAKNET_MAP_H
#include "DS_OrderedList.h"
#include "Export.h"
#include "RakMemoryOverride.h"
#include "RakAssert.h"
// If I want to change this to a red-black tree, this is a good site: http://www.cs.auckland.ac.nz/software/AlgAnim/red_black.html
// This makes insertions and deletions faster. But then traversals are slow, while they are currently fast.
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
/// The default comparison has to be first so it can be called as a default parameter.
/// It then is followed by MapNode, followed by NodeComparisonFunc
template <class key_type>
int defaultMapKeyComparison(const key_type &a, const key_type &b)
{
if (a<b) return -1; if (a==b) return 0; return 1;
}
/// \note IMPORTANT! If you use defaultMapKeyComparison then call IMPLEMENT_DEFAULT_COMPARISON or you will get an unresolved external linker error.
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&, const key_type&)=defaultMapKeyComparison<key_type> >
class RAK_DLL_EXPORT Map
{
public:
static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison<key_type>(key_type(),key_type());}
struct MapNode
{
MapNode() {}
MapNode(key_type _key, data_type _data) : mapNodeKey(_key), mapNodeData(_data) {}
MapNode& operator = ( const MapNode& input ) {mapNodeKey=input.mapNodeKey; mapNodeData=input.mapNodeData; return *this;}
MapNode( const MapNode & input) {mapNodeKey=input.mapNodeKey; mapNodeData=input.mapNodeData;}
key_type mapNodeKey;
data_type mapNodeData;
};
// Has to be a static because the comparison callback for DataStructures::OrderedList is a C function
static int NodeComparisonFunc(const key_type &a, const MapNode &b)
{
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
return key_comparison_func(a, b.mapNodeKey);
}
Map();
~Map();
Map( const Map& original_copy );
Map& operator= ( const Map& original_copy );
data_type& Get(const key_type &key) const;
data_type Pop(const key_type &key);
// Add if needed
void Set(const key_type &key, const data_type &data);
// Must already exist
void SetExisting(const key_type &key, const data_type &data);
// Must add
void SetNew(const key_type &key, const data_type &data);
bool Has(const key_type &key) const;
bool Delete(const key_type &key);
data_type& operator[] ( const unsigned int position ) const;
key_type GetKeyAtIndex( const unsigned int position ) const;
unsigned GetIndexAtKey( const key_type &key );
void RemoveAtIndex(const unsigned index);
void Clear(void);
unsigned Size(void) const;
protected:
DataStructures::OrderedList< key_type,MapNode,&Map::NodeComparisonFunc > mapNodeList;
void SaveLastSearch(const key_type &key, unsigned index) const;
bool HasSavedSearchResult(const key_type &key) const;
unsigned lastSearchIndex;
key_type lastSearchKey;
bool lastSearchIndexValid;
};
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
Map<key_type, data_type, key_comparison_func>::Map()
{
lastSearchIndexValid=false;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
Map<key_type, data_type, key_comparison_func>::~Map()
{
Clear();
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
Map<key_type, data_type, key_comparison_func>::Map( const Map& original_copy )
{
mapNodeList=original_copy.mapNodeList;
lastSearchIndex=original_copy.lastSearchIndex;
lastSearchKey=original_copy.lastSearchKey;
lastSearchIndexValid=original_copy.lastSearchIndexValid;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
Map<key_type, data_type, key_comparison_func>& Map<key_type, data_type, key_comparison_func>::operator= ( const Map& original_copy )
{
mapNodeList=original_copy.mapNodeList;
lastSearchIndex=original_copy.lastSearchIndex;
lastSearchKey=original_copy.lastSearchKey;
lastSearchIndexValid=original_copy.lastSearchIndexValid;
return *this;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
data_type& Map<key_type, data_type, key_comparison_func>::Get(const key_type &key) const
{
if (HasSavedSearchResult(key))
return mapNodeList[lastSearchIndex].mapNodeData;
bool objectExists;
unsigned index;
index=mapNodeList.GetIndexFromKey(key, &objectExists);
RakAssert(objectExists);
SaveLastSearch(key,index);
return mapNodeList[index].mapNodeData;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
unsigned Map<key_type, data_type, key_comparison_func>::GetIndexAtKey( const key_type &key )
{
if (HasSavedSearchResult(key))
return lastSearchIndex;
bool objectExists;
unsigned index;
index=mapNodeList.GetIndexFromKey(key, &objectExists);
if (objectExists==false)
{
RakAssert(objectExists);
}
SaveLastSearch(key,index);
return index;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::RemoveAtIndex(const unsigned index)
{
mapNodeList.RemoveAtIndex(index);
lastSearchIndexValid=false;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
data_type Map<key_type, data_type, key_comparison_func>::Pop(const key_type &key)
{
bool objectExists;
unsigned index;
if (HasSavedSearchResult(key))
index=lastSearchIndex;
else
{
index=mapNodeList.GetIndexFromKey(key, &objectExists);
RakAssert(objectExists);
}
data_type tmp = mapNodeList[index].mapNodeData;
mapNodeList.RemoveAtIndex(index);
lastSearchIndexValid=false;
return tmp;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::Set(const key_type &key, const data_type &data)
{
bool objectExists;
unsigned index;
if (HasSavedSearchResult(key))
{
mapNodeList[lastSearchIndex].mapNodeData=data;
return;
}
index=mapNodeList.GetIndexFromKey(key, &objectExists);
if (objectExists)
{
SaveLastSearch(key,index);
mapNodeList[index].mapNodeData=data;
}
else
{
SaveLastSearch(key,mapNodeList.Insert(key,MapNode(key,data), true, _FILE_AND_LINE_));
}
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::SetExisting(const key_type &key, const data_type &data)
{
bool objectExists;
unsigned index;
if (HasSavedSearchResult(key))
{
index=lastSearchIndex;
}
else
{
index=mapNodeList.GetIndexFromKey(key, &objectExists);
RakAssert(objectExists);
SaveLastSearch(key,index);
}
mapNodeList[index].mapNodeData=data;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::SetNew(const key_type &key, const data_type &data)
{
#ifdef _DEBUG
bool objectExists;
mapNodeList.GetIndexFromKey(key, &objectExists);
RakAssert(objectExists==false);
#endif
SaveLastSearch(key,mapNodeList.Insert(key,MapNode(key,data), true, _FILE_AND_LINE_));
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
bool Map<key_type, data_type, key_comparison_func>::Has(const key_type &key) const
{
if (HasSavedSearchResult(key))
return true;
bool objectExists;
unsigned index;
index=mapNodeList.GetIndexFromKey(key, &objectExists);
if (objectExists)
SaveLastSearch(key,index);
return objectExists;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
bool Map<key_type, data_type, key_comparison_func>::Delete(const key_type &key)
{
if (HasSavedSearchResult(key))
{
lastSearchIndexValid=false;
mapNodeList.RemoveAtIndex(lastSearchIndex);
return true;
}
bool objectExists;
unsigned index;
index=mapNodeList.GetIndexFromKey(key, &objectExists);
if (objectExists)
{
lastSearchIndexValid=false;
mapNodeList.RemoveAtIndex(index);
return true;
}
else
return false;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::Clear(void)
{
lastSearchIndexValid=false;
mapNodeList.Clear(false, _FILE_AND_LINE_);
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
data_type& Map<key_type, data_type, key_comparison_func>::operator[]( const unsigned int position ) const
{
return mapNodeList[position].mapNodeData;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
key_type Map<key_type, data_type, key_comparison_func>::GetKeyAtIndex( const unsigned int position ) const
{
return mapNodeList[position].mapNodeKey;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
unsigned Map<key_type, data_type, key_comparison_func>::Size(void) const
{
return mapNodeList.Size();
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::SaveLastSearch(const key_type &key, const unsigned index) const
{
(void) key;
(void) index;
/*
lastSearchIndex=index;
lastSearchKey=key;
lastSearchIndexValid=true;
*/
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
bool Map<key_type, data_type, key_comparison_func>::HasSavedSearchResult(const key_type &key) const
{
(void) key;
// Not threadsafe!
return false;
// return lastSearchIndexValid && key_comparison_func(key,lastSearchKey)==0;
}
}
#endif

View File

@@ -0,0 +1,294 @@
/// \file DS_MemoryPool.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __MEMORY_POOL_H
#define __MEMORY_POOL_H
#ifndef __APPLE__
// Use stdlib and not malloc for compatibility
#include <stdlib.h>
#endif
#include "RakAssert.h"
#include "Export.h"
#include "RakMemoryOverride.h"
// DS_MEMORY_POOL_MAX_FREE_PAGES must be > 1
#define DS_MEMORY_POOL_MAX_FREE_PAGES 4
//#define _DISABLE_MEMORY_POOL
namespace DataStructures
{
/// Very fast memory pool for allocating and deallocating structures that don't have constructors or destructors.
/// Contains a list of pages, each of which has an array of the user structures
template <class MemoryBlockType>
class RAK_DLL_EXPORT MemoryPool
{
public:
struct Page;
struct MemoryWithPage
{
MemoryBlockType userMemory;
Page *parentPage;
};
struct Page
{
MemoryWithPage** availableStack;
int availableStackSize;
MemoryWithPage* block;
Page *next, *prev;
};
MemoryPool();
~MemoryPool();
void SetPageSize(int size); // Defaults to 16384 bytes
MemoryBlockType *Allocate(const char *file, unsigned int line);
void Release(MemoryBlockType *m, const char *file, unsigned int line);
void Clear(const char *file, unsigned int line);
int GetAvailablePagesSize(void) const {return availablePagesSize;}
int GetUnavailablePagesSize(void) const {return unavailablePagesSize;}
int GetMemoryPoolPageSize(void) const {return memoryPoolPageSize;}
protected:
int BlocksPerPage(void) const;
void AllocateFirst(void);
bool InitPage(Page *page, Page *prev, const char *file, unsigned int line);
// availablePages contains pages which have room to give the user new blocks. We return these blocks from the head of the list
// unavailablePages are pages which are totally full, and from which we do not return new blocks.
// Pages move from the head of unavailablePages to the tail of availablePages, and from the head of availablePages to the tail of unavailablePages
Page *availablePages, *unavailablePages;
int availablePagesSize, unavailablePagesSize;
int memoryPoolPageSize;
};
template<class MemoryBlockType>
MemoryPool<MemoryBlockType>::MemoryPool()
{
#ifndef _DISABLE_MEMORY_POOL
//AllocateFirst();
availablePagesSize=0;
unavailablePagesSize=0;
memoryPoolPageSize=16384;
#endif
}
template<class MemoryBlockType>
MemoryPool<MemoryBlockType>::~MemoryPool()
{
#ifndef _DISABLE_MEMORY_POOL
Clear(_FILE_AND_LINE_);
#endif
}
template<class MemoryBlockType>
void MemoryPool<MemoryBlockType>::SetPageSize(int size)
{
memoryPoolPageSize=size;
}
template<class MemoryBlockType>
MemoryBlockType* MemoryPool<MemoryBlockType>::Allocate(const char *file, unsigned int line)
{
#ifdef _DISABLE_MEMORY_POOL
return (MemoryBlockType*) rakMalloc_Ex(sizeof(MemoryBlockType), file, line);
#else
if (availablePagesSize>0)
{
MemoryBlockType *retVal;
Page *curPage;
curPage=availablePages;
retVal = (MemoryBlockType*) curPage->availableStack[--(curPage->availableStackSize)];
if (curPage->availableStackSize==0)
{
--availablePagesSize;
availablePages=curPage->next;
RakAssert(availablePagesSize==0 || availablePages->availableStackSize>0);
curPage->next->prev=curPage->prev;
curPage->prev->next=curPage->next;
if (unavailablePagesSize++==0)
{
unavailablePages=curPage;
curPage->next=curPage;
curPage->prev=curPage;
}
else
{
curPage->next=unavailablePages;
curPage->prev=unavailablePages->prev;
unavailablePages->prev->next=curPage;
unavailablePages->prev=curPage;
}
}
RakAssert(availablePagesSize==0 || availablePages->availableStackSize>0);
return retVal;
}
availablePages = (Page *) rakMalloc_Ex(sizeof(Page), file, line);
if (availablePages==0)
return 0;
availablePagesSize=1;
if (InitPage(availablePages, availablePages, file, line)==false)
return 0;
// If this assert hits, we couldn't allocate even 1 block per page. Increase the page size
RakAssert(availablePages->availableStackSize>1);
return (MemoryBlockType *) availablePages->availableStack[--availablePages->availableStackSize];
#endif
}
template<class MemoryBlockType>
void MemoryPool<MemoryBlockType>::Release(MemoryBlockType *m, const char *file, unsigned int line)
{
#ifdef _DISABLE_MEMORY_POOL
rakFree_Ex(m, file, line);
return;
#else
// Find the page this block is in and return it.
Page *curPage;
MemoryWithPage *memoryWithPage = (MemoryWithPage*)m;
curPage=memoryWithPage->parentPage;
if (curPage->availableStackSize==0)
{
// The page is in the unavailable list so move it to the available list
curPage->availableStack[curPage->availableStackSize++]=memoryWithPage;
unavailablePagesSize--;
// As this page is no longer totally empty, move it to the end of available pages
curPage->next->prev=curPage->prev;
curPage->prev->next=curPage->next;
if (unavailablePagesSize>0 && curPage==unavailablePages)
unavailablePages=unavailablePages->next;
if (availablePagesSize++==0)
{
availablePages=curPage;
curPage->next=curPage;
curPage->prev=curPage;
}
else
{
curPage->next=availablePages;
curPage->prev=availablePages->prev;
availablePages->prev->next=curPage;
availablePages->prev=curPage;
}
}
else
{
curPage->availableStack[curPage->availableStackSize++]=memoryWithPage;
if (curPage->availableStackSize==BlocksPerPage() &&
availablePagesSize>=DS_MEMORY_POOL_MAX_FREE_PAGES)
{
// After a certain point, just deallocate empty pages rather than keep them around
if (curPage==availablePages)
{
availablePages=curPage->next;
RakAssert(availablePages->availableStackSize>0);
}
curPage->prev->next=curPage->next;
curPage->next->prev=curPage->prev;
availablePagesSize--;
rakFree_Ex(curPage->availableStack, file, line );
rakFree_Ex(curPage->block, file, line );
rakFree_Ex(curPage, file, line );
}
}
#endif
}
template<class MemoryBlockType>
void MemoryPool<MemoryBlockType>::Clear(const char *file, unsigned int line)
{
#ifdef _DISABLE_MEMORY_POOL
return;
#else
Page *cur, *freed;
if (availablePagesSize>0)
{
cur = availablePages;
#ifdef _MSC_VER
#pragma warning(disable:4127) // conditional expression is constant
#endif
while (true)
// do
{
rakFree_Ex(cur->availableStack, file, line );
rakFree_Ex(cur->block, file, line );
freed=cur;
cur=cur->next;
if (cur==availablePages)
{
rakFree_Ex(freed, file, line );
break;
}
rakFree_Ex(freed, file, line );
}// while(cur!=availablePages);
}
if (unavailablePagesSize>0)
{
cur = unavailablePages;
while (1)
//do
{
rakFree_Ex(cur->availableStack, file, line );
rakFree_Ex(cur->block, file, line );
freed=cur;
cur=cur->next;
if (cur==unavailablePages)
{
rakFree_Ex(freed, file, line );
break;
}
rakFree_Ex(freed, file, line );
} // while(cur!=unavailablePages);
}
availablePagesSize=0;
unavailablePagesSize=0;
#endif
}
template<class MemoryBlockType>
int MemoryPool<MemoryBlockType>::BlocksPerPage(void) const
{
return memoryPoolPageSize / sizeof(MemoryWithPage);
}
template<class MemoryBlockType>
bool MemoryPool<MemoryBlockType>::InitPage(Page *page, Page *prev, const char *file, unsigned int line)
{
int i=0;
const int bpp = BlocksPerPage();
page->block=(MemoryWithPage*) rakMalloc_Ex(memoryPoolPageSize, file, line);
if (page->block==0)
return false;
page->availableStack=(MemoryWithPage**)rakMalloc_Ex(sizeof(MemoryWithPage*)*bpp, file, line);
if (page->availableStack==0)
{
rakFree_Ex(page->block, file, line );
return false;
}
MemoryWithPage *curBlock = page->block;
MemoryWithPage **curStack = page->availableStack;
while (i < bpp)
{
curBlock->parentPage=page;
curStack[i]=curBlock++;
i++;
}
page->availableStackSize=bpp;
page->next=availablePages;
page->prev=prev;
return true;
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,244 @@
/// \file DS_OrderedChannelHeap.h
/// \internal
/// \brief Ordered Channel Heap . This is a heap where you add to it on multiple ordered channels, with each channel having a different weight.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __RAKNET_ORDERED_CHANNEL_HEAP_H
#define __RAKNET_ORDERED_CHANNEL_HEAP_H
#include "DS_Heap.h"
#include "DS_Map.h"
#include "DS_Queue.h"
#include "Export.h"
#include "RakAssert.h"
#include "Rand.h"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)=defaultMapKeyComparison<channel_key_type> >
class RAK_DLL_EXPORT OrderedChannelHeap
{
public:
static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison<channel_key_type>(channel_key_type(),channel_key_type());}
OrderedChannelHeap();
~OrderedChannelHeap();
void Push(const channel_key_type &channelID, const heap_data_type &data);
void PushAtHead(const unsigned index, const channel_key_type &channelID, const heap_data_type &data);
heap_data_type Pop(const unsigned startingIndex=0);
heap_data_type Peek(const unsigned startingIndex) const;
void AddChannel(const channel_key_type &channelID, const double weight);
void RemoveChannel(channel_key_type channelID);
void Clear(void);
heap_data_type& operator[] ( const unsigned int position ) const;
unsigned ChannelSize(const channel_key_type &channelID);
unsigned Size(void) const;
struct QueueAndWeight
{
DataStructures::Queue<double> randResultQueue;
double weight;
bool signalDeletion;
};
struct HeapChannelAndData
{
HeapChannelAndData() {}
HeapChannelAndData(const channel_key_type &_channel, const heap_data_type &_data) : data(_data), channel(_channel) {}
heap_data_type data;
channel_key_type channel;
};
protected:
DataStructures::Map<channel_key_type, QueueAndWeight*, channel_key_comparison_func> map;
DataStructures::Heap<double, HeapChannelAndData, true> heap;
void GreatestRandResult(void);
};
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::OrderedChannelHeap()
{
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::~OrderedChannelHeap()
{
Clear();
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::Push(const channel_key_type &channelID, const heap_data_type &data)
{
PushAtHead(MAX_UNSIGNED_LONG, channelID, data);
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::GreatestRandResult(void)
{
double greatest;
unsigned i;
greatest=0.0;
for (i=0; i < map.Size(); i++)
{
if (map[i]->randResultQueue.Size() && map[i]->randResultQueue[0]>greatest)
greatest=map[i]->randResultQueue[0];
}
return greatest;
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::PushAtHead(const unsigned index, const channel_key_type &channelID, const heap_data_type &data)
{
// If an assert hits here then this is an unknown channel. Call AddChannel first.
QueueAndWeight *queueAndWeight=map.Get(channelID);
double maxRange, minRange, rnd;
if (queueAndWeight->randResultQueue.Size()==0)
{
// Set maxRange to the greatest random number waiting to be returned, rather than 1.0 necessarily
// This is so weights are scaled similarly among channels. For example, if the head weight for a used channel was .25
// and then we added another channel, the new channel would need to choose between .25 and 0
// If we chose between 1.0 and 0, it would be 1/.25 (4x) more likely to be at the head of the heap than it should be
maxRange=GreatestRandResult();
if (maxRange==0.0)
maxRange=1.0;
minRange=0.0;
}
else if (index >= queueAndWeight->randResultQueue.Size())
{
maxRange=queueAndWeight->randResultQueue[queueAndWeight->randResultQueue.Size()-1]*.99999999;
minRange=0.0;
}
else
{
if (index==0)
{
maxRange=GreatestRandResult();
if (maxRange==queueAndWeight->randResultQueue[0])
maxRange=1.0;
}
else if (index >= queueAndWeight->randResultQueue.Size())
maxRange=queueAndWeight->randResultQueue[queueAndWeight->randResultQueue.Size()-1]*.99999999;
else
maxRange=queueAndWeight->randResultQueue[index-1]*.99999999;
minRange=maxRange=queueAndWeight->randResultQueue[index]*1.00000001;
}
#ifdef _DEBUG
RakAssert(maxRange!=0.0);
#endif
rnd=frandomMT() * (maxRange - minRange);
if (rnd==0.0)
rnd=maxRange/2.0;
if (index >= queueAndWeight->randResultQueue.Size())
queueAndWeight->randResultQueue.Push(rnd);
else
queueAndWeight->randResultQueue.PushAtHead(rnd, index);
heap.Push(rnd*queueAndWeight->weight, HeapChannelAndData(channelID, data));
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
heap_data_type OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::Pop(const unsigned startingIndex)
{
RakAssert(startingIndex < heap.Size());
QueueAndWeight *queueAndWeight=map.Get(heap[startingIndex].channel);
if (startingIndex!=0)
{
// Ugly - have to count in the heap how many nodes have the same channel, so we know where to delete from in the queue
unsigned indiceCount=0;
unsigned i;
for (i=0; i < startingIndex; i++)
if (channel_key_comparison_func(heap[i].channel,heap[startingIndex].channel)==0)
indiceCount++;
queueAndWeight->randResultQueue.RemoveAtIndex(indiceCount);
}
else
{
// TODO - ordered channel heap uses progressively lower values as items are inserted. But this won't give relative ordering among channels. I have to renormalize after every pop.
queueAndWeight->randResultQueue.Pop();
}
// Try to remove the channel after every pop, because doing so is not valid while there are elements in the list.
if (queueAndWeight->signalDeletion)
RemoveChannel(heap[startingIndex].channel);
return heap.Pop(startingIndex).data;
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
heap_data_type OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::Peek(const unsigned startingIndex) const
{
HeapChannelAndData heapChannelAndData = heap.Peek(startingIndex);
return heapChannelAndData.data;
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::AddChannel(const channel_key_type &channelID, const double weight)
{
QueueAndWeight *qaw = RakNet::OP_NEW<QueueAndWeight>( _FILE_AND_LINE_ );
qaw->weight=weight;
qaw->signalDeletion=false;
map.SetNew(channelID, qaw);
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::RemoveChannel(channel_key_type channelID)
{
if (map.Has(channelID))
{
unsigned i;
i=map.GetIndexAtKey(channelID);
if (map[i]->randResultQueue.Size()==0)
{
RakNet::OP_DELETE(map[i], _FILE_AND_LINE_);
map.RemoveAtIndex(i);
}
else
{
// Signal this channel for deletion later, because the heap has nodes with this channel right now
map[i]->signalDeletion=true;
}
}
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
unsigned OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::Size(void) const
{
return heap.Size();
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
heap_data_type& OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::operator[]( const unsigned int position ) const
{
return heap[position].data;
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
unsigned OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::ChannelSize(const channel_key_type &channelID)
{
QueueAndWeight *queueAndWeight=map.Get(channelID);
return queueAndWeight->randResultQueue.Size();
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::Clear(void)
{
unsigned i;
for (i=0; i < map.Size(); i++)
RakNet::OP_DELETE(map[i], _FILE_AND_LINE_);
map.Clear(_FILE_AND_LINE_);
heap.Clear(_FILE_AND_LINE_);
}
}
#endif

View File

@@ -0,0 +1,263 @@
/// \file DS_OrderedList.h
/// \internal
/// \brief Quicksort ordered list.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "DS_List.h"
#include "RakMemoryOverride.h"
#include "Export.h"
#ifndef __ORDERED_LIST_H
#define __ORDERED_LIST_H
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
template <class key_type, class data_type>
int defaultOrderedListComparison(const key_type &a, const data_type &b)
{
if (a<b) return -1; if (a==b) return 0; return 1;
}
/// \note IMPORTANT! If you use defaultOrderedListComparison then call IMPLEMENT_DEFAULT_COMPARISON or you will get an unresolved external linker error.
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)=defaultOrderedListComparison<key_type, data_type> >
class RAK_DLL_EXPORT OrderedList
{
public:
static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultOrderedListComparison<key_type, data_type>(key_type(),data_type());}
OrderedList();
~OrderedList();
OrderedList( const OrderedList& original_copy );
OrderedList& operator= ( const OrderedList& original_copy );
/// comparisonFunction must take a key_type and a data_type and return <0, ==0, or >0
/// If the data type has comparison operators already defined then you can just use defaultComparison
bool HasData(const key_type &key, int (*cf)(const key_type&, const data_type&)=default_comparison_function) const;
// GetIndexFromKey returns where the insert should go at the same time checks if it is there
unsigned GetIndexFromKey(const key_type &key, bool *objectExists, int (*cf)(const key_type&, const data_type&)=default_comparison_function) const;
data_type GetElementFromKey(const key_type &key, int (*cf)(const key_type&, const data_type&)=default_comparison_function) const;
bool GetElementFromKey(const key_type &key, data_type &element, int (*cf)(const key_type&, const data_type&)=default_comparison_function) const;
unsigned Insert(const key_type &key, const data_type &data, bool assertOnDuplicate, const char *file, unsigned int line, int (*cf)(const key_type&, const data_type&)=default_comparison_function);
unsigned Remove(const key_type &key, int (*cf)(const key_type&, const data_type&)=default_comparison_function);
unsigned RemoveIfExists(const key_type &key, int (*cf)(const key_type&, const data_type&)=default_comparison_function);
data_type& operator[] ( const unsigned int position ) const;
void RemoveAtIndex(const unsigned index);
void InsertAtIndex(const data_type &data, const unsigned index, const char *file, unsigned int line);
void InsertAtEnd(const data_type &data, const char *file, unsigned int line);
void RemoveFromEnd(const unsigned num=1);
void Clear(bool doNotDeallocate, const char *file, unsigned int line);
unsigned Size(void) const;
protected:
DataStructures::List<data_type> orderedList;
};
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
OrderedList<key_type, data_type, default_comparison_function>::OrderedList()
{
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
OrderedList<key_type, data_type, default_comparison_function>::~OrderedList()
{
Clear(false, _FILE_AND_LINE_);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
OrderedList<key_type, data_type, default_comparison_function>::OrderedList( const OrderedList& original_copy )
{
orderedList=original_copy.orderedList;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
OrderedList<key_type, data_type, default_comparison_function>& OrderedList<key_type, data_type, default_comparison_function>::operator= ( const OrderedList& original_copy )
{
orderedList=original_copy.orderedList;
return *this;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
bool OrderedList<key_type, data_type, default_comparison_function>::HasData(const key_type &key, int (*cf)(const key_type&, const data_type&)) const
{
bool objectExists;
GetIndexFromKey(key, &objectExists, cf);
return objectExists;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
data_type OrderedList<key_type, data_type, default_comparison_function>::GetElementFromKey(const key_type &key, int (*cf)(const key_type&, const data_type&)) const
{
bool objectExists;
unsigned index;
index = GetIndexFromKey(key, &objectExists, cf);
RakAssert(objectExists);
return orderedList[index];
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
bool OrderedList<key_type, data_type, default_comparison_function>::GetElementFromKey(const key_type &key, data_type &element, int (*cf)(const key_type&, const data_type&)) const
{
bool objectExists;
unsigned index;
index = GetIndexFromKey(key, &objectExists, cf);
if (objectExists)
element = orderedList[index];
return objectExists;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
unsigned OrderedList<key_type, data_type, default_comparison_function>::GetIndexFromKey(const key_type &key, bool *objectExists, int (*cf)(const key_type&, const data_type&)) const
{
int index, upperBound, lowerBound;
int res;
if (orderedList.Size()==0)
{
*objectExists=false;
return 0;
}
upperBound=(int)orderedList.Size()-1;
lowerBound=0;
index = (int)orderedList.Size()/2;
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while (1)
{
res = cf(key,orderedList[index]);
if (res==0)
{
*objectExists=true;
return index;
}
else if (res<0)
{
upperBound=index-1;
}
else// if (res>0)
{
lowerBound=index+1;
}
index=lowerBound+(upperBound-lowerBound)/2;
if (lowerBound>upperBound)
{
*objectExists=false;
return lowerBound; // No match
}
}
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
unsigned OrderedList<key_type, data_type, default_comparison_function>::Insert(const key_type &key, const data_type &data, bool assertOnDuplicate, const char *file, unsigned int line, int (*cf)(const key_type&, const data_type&))
{
(void) assertOnDuplicate;
bool objectExists;
unsigned index;
index = GetIndexFromKey(key, &objectExists, cf);
// Don't allow duplicate insertion.
if (objectExists)
{
// This is usually a bug!
RakAssert(assertOnDuplicate==false);
return (unsigned)-1;
}
if (index>=orderedList.Size())
{
orderedList.Insert(data, file, line);
return orderedList.Size()-1;
}
else
{
orderedList.Insert(data,index, file, line);
return index;
}
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
unsigned OrderedList<key_type, data_type, default_comparison_function>::Remove(const key_type &key, int (*cf)(const key_type&, const data_type&))
{
bool objectExists;
unsigned index;
index = GetIndexFromKey(key, &objectExists, cf);
// Can't find the element to remove if this assert hits
// RakAssert(objectExists==true);
if (objectExists==false)
{
RakAssert(objectExists==true);
return 0;
}
orderedList.RemoveAtIndex(index);
return index;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
unsigned OrderedList<key_type, data_type, default_comparison_function>::RemoveIfExists(const key_type &key, int (*cf)(const key_type&, const data_type&))
{
bool objectExists;
unsigned index;
index = GetIndexFromKey(key, &objectExists, cf);
// Can't find the element to remove if this assert hits
if (objectExists==false)
return 0;
orderedList.RemoveAtIndex(index);
return index;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
void OrderedList<key_type, data_type, default_comparison_function>::RemoveAtIndex(const unsigned index)
{
orderedList.RemoveAtIndex(index);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
void OrderedList<key_type, data_type, default_comparison_function>::InsertAtIndex(const data_type &data, const unsigned index, const char *file, unsigned int line)
{
orderedList.Insert(data, index, file, line);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
void OrderedList<key_type, data_type, default_comparison_function>::InsertAtEnd(const data_type &data, const char *file, unsigned int line)
{
orderedList.Insert(data, file, line);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
void OrderedList<key_type, data_type, default_comparison_function>::RemoveFromEnd(const unsigned num)
{
orderedList.RemoveFromEnd(num);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
void OrderedList<key_type, data_type, default_comparison_function>::Clear(bool doNotDeallocate, const char *file, unsigned int line)
{
orderedList.Clear(doNotDeallocate, file, line);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
data_type& OrderedList<key_type, data_type, default_comparison_function>::operator[]( const unsigned int position ) const
{
return orderedList[position];
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
unsigned OrderedList<key_type, data_type, default_comparison_function>::Size(void) const
{
return orderedList.Size();
}
}
#endif

View File

@@ -0,0 +1,435 @@
/// \file DS_Queue.h
/// \internal
/// \brief A queue used by RakNet.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __QUEUE_H
#define __QUEUE_H
// Template classes have to have all the code in the header file
#include "RakAssert.h"
#include "Export.h"
#include "RakMemoryOverride.h"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
/// \brief A queue implemented as an array with a read and write index.
template <class queue_type>
class RAK_DLL_EXPORT Queue
{
public:
Queue();
~Queue();
Queue( Queue& original_copy );
bool operator= ( const Queue& original_copy );
void Push( const queue_type& input, const char *file, unsigned int line );
void PushAtHead( const queue_type& input, unsigned index, const char *file, unsigned int line );
queue_type& operator[] ( unsigned int position ) const; // Not a normal thing you do with a queue but can be used for efficiency
void RemoveAtIndex( unsigned int position ); // Not a normal thing you do with a queue but can be used for efficiency
inline queue_type Peek( void ) const;
inline queue_type PeekTail( void ) const;
inline queue_type Pop( void );
// Debug: Set pointer to 0, for memory leak detection
inline queue_type PopDeref( void );
inline unsigned int Size( void ) const;
inline bool IsEmpty(void) const;
inline unsigned int AllocationSize( void ) const;
inline void Clear( const char *file, unsigned int line );
void Compress( const char *file, unsigned int line );
bool Find ( queue_type q );
void ClearAndForceAllocation( int size, const char *file, unsigned int line ); // Force a memory allocation to a certain larger size
private:
queue_type* array;
unsigned int head; // Array index for the head of the queue
unsigned int tail; // Array index for the tail of the queue
unsigned int allocation_size;
};
template <class queue_type>
inline unsigned int Queue<queue_type>::Size( void ) const
{
if ( head <= tail )
return tail -head;
else
return allocation_size -head + tail;
}
template <class queue_type>
inline bool Queue<queue_type>::IsEmpty(void) const
{
return head==tail;
}
template <class queue_type>
inline unsigned int Queue<queue_type>::AllocationSize( void ) const
{
return allocation_size;
}
template <class queue_type>
Queue<queue_type>::Queue()
{
//allocation_size = 16;
//array = RakNet::OP_NEW_ARRAY<queue_type>(allocation_size, _FILE_AND_LINE_ );
allocation_size = 0;
array=0;
head = 0;
tail = 0;
}
template <class queue_type>
Queue<queue_type>::~Queue()
{
if (allocation_size>0)
RakNet::OP_DELETE_ARRAY(array, _FILE_AND_LINE_);
}
template <class queue_type>
inline queue_type Queue<queue_type>::Pop( void )
{
#ifdef _DEBUG
RakAssert( head != tail);
#endif
//head=(head+1) % allocation_size;
if ( ++head == allocation_size )
head = 0;
if ( head == 0 )
return ( queue_type ) array[ allocation_size -1 ];
return ( queue_type ) array[ head -1 ];
}
template <class queue_type>
inline queue_type Queue<queue_type>::PopDeref( void )
{
if ( ++head == allocation_size )
head = 0;
queue_type q;
if ( head == 0 )
{
q=array[ allocation_size -1 ];
array[ allocation_size -1 ]=0;
return q;
}
q=array[ head -1 ];
array[ head -1 ]=0;
return q;
}
template <class queue_type>
void Queue<queue_type>::PushAtHead( const queue_type& input, unsigned index, const char *file, unsigned int line )
{
RakAssert(index <= Size());
// Just force a reallocation, will be overwritten
Push(input, file, line );
if (Size()==1)
return;
unsigned writeIndex, readIndex, trueWriteIndex, trueReadIndex;
writeIndex=Size()-1;
readIndex=writeIndex-1;
while (readIndex >= index)
{
if ( head + writeIndex >= allocation_size )
trueWriteIndex = head + writeIndex - allocation_size;
else
trueWriteIndex = head + writeIndex;
if ( head + readIndex >= allocation_size )
trueReadIndex = head + readIndex - allocation_size;
else
trueReadIndex = head + readIndex;
array[trueWriteIndex]=array[trueReadIndex];
if (readIndex==0)
break;
writeIndex--;
readIndex--;
}
if ( head + index >= allocation_size )
trueWriteIndex = head + index - allocation_size;
else
trueWriteIndex = head + index;
array[trueWriteIndex]=input;
}
template <class queue_type>
inline queue_type Queue<queue_type>::Peek( void ) const
{
#ifdef _DEBUG
RakAssert( head != tail );
#endif
return ( queue_type ) array[ head ];
}
template <class queue_type>
inline queue_type Queue<queue_type>::PeekTail( void ) const
{
#ifdef _DEBUG
RakAssert( head != tail );
#endif
if (tail!=0)
return ( queue_type ) array[ tail-1 ];
else
return ( queue_type ) array[ allocation_size-1 ];
}
template <class queue_type>
void Queue<queue_type>::Push( const queue_type& input, const char *file, unsigned int line )
{
if ( allocation_size == 0 )
{
array = RakNet::OP_NEW_ARRAY<queue_type>(16, file, line );
head = 0;
tail = 1;
array[ 0 ] = input;
allocation_size = 16;
return ;
}
array[ tail++ ] = input;
if ( tail == allocation_size )
tail = 0;
if ( tail == head )
{
// unsigned int index=tail;
// Need to allocate more memory.
queue_type * new_array;
new_array = RakNet::OP_NEW_ARRAY<queue_type>(allocation_size * 2, file, line );
#ifdef _DEBUG
RakAssert( new_array );
#endif
if (new_array==0)
return;
for ( unsigned int counter = 0; counter < allocation_size; ++counter )
new_array[ counter ] = array[ ( head + counter ) % ( allocation_size ) ];
head = 0;
tail = allocation_size;
allocation_size *= 2;
// Delete the old array and move the pointer to the new array
RakNet::OP_DELETE_ARRAY(array, file, line);
array = new_array;
}
}
template <class queue_type>
Queue<queue_type>::Queue( Queue& original_copy )
{
// Allocate memory for copy
if ( original_copy.Size() == 0 )
{
allocation_size = 0;
}
else
{
array = RakNet::OP_NEW_ARRAY<queue_type >( original_copy.Size() + 1 , _FILE_AND_LINE_ );
for ( unsigned int counter = 0; counter < original_copy.Size(); ++counter )
array[ counter ] = original_copy.array[ ( original_copy.head + counter ) % ( original_copy.allocation_size ) ];
head = 0;
tail = original_copy.Size();
allocation_size = original_copy.Size() + 1;
}
}
template <class queue_type>
bool Queue<queue_type>::operator= ( const Queue& original_copy )
{
if ( ( &original_copy ) == this )
return false;
Clear(_FILE_AND_LINE_);
// Allocate memory for copy
if ( original_copy.Size() == 0 )
{
allocation_size = 0;
}
else
{
array = RakNet::OP_NEW_ARRAY<queue_type >( original_copy.Size() + 1 , _FILE_AND_LINE_ );
for ( unsigned int counter = 0; counter < original_copy.Size(); ++counter )
array[ counter ] = original_copy.array[ ( original_copy.head + counter ) % ( original_copy.allocation_size ) ];
head = 0;
tail = original_copy.Size();
allocation_size = original_copy.Size() + 1;
}
return true;
}
template <class queue_type>
inline void Queue<queue_type>::Clear ( const char *file, unsigned int line )
{
if ( allocation_size == 0 )
return ;
if (allocation_size > 32)
{
RakNet::OP_DELETE_ARRAY(array, file, line);
allocation_size = 0;
}
head = 0;
tail = 0;
}
template <class queue_type>
void Queue<queue_type>::Compress ( const char *file, unsigned int line )
{
queue_type* new_array;
unsigned int newAllocationSize;
if (allocation_size==0)
return;
newAllocationSize=1;
while (newAllocationSize <= Size())
newAllocationSize<<=1; // Must be a better way to do this but I'm too dumb to figure it out quickly :)
new_array = RakNet::OP_NEW_ARRAY<queue_type >(newAllocationSize, file, line );
for (unsigned int counter=0; counter < Size(); ++counter)
new_array[counter] = array[(head + counter)%(allocation_size)];
tail=Size();
allocation_size=newAllocationSize;
head=0;
// Delete the old array and move the pointer to the new array
RakNet::OP_DELETE_ARRAY(array, file, line);
array=new_array;
}
template <class queue_type>
bool Queue<queue_type>::Find ( queue_type q )
{
if ( allocation_size == 0 )
return false;
unsigned int counter = head;
while ( counter != tail )
{
if ( array[ counter ] == q )
return true;
counter = ( counter + 1 ) % allocation_size;
}
return false;
}
template <class queue_type>
void Queue<queue_type>::ClearAndForceAllocation( int size, const char *file, unsigned int line )
{
RakNet::OP_DELETE_ARRAY(array, file, line);
if (size>0)
array = RakNet::OP_NEW_ARRAY<queue_type>(size, file, line );
else
array=0;
allocation_size = size;
head = 0;
tail = 0;
}
template <class queue_type>
inline queue_type& Queue<queue_type>::operator[] ( unsigned int position ) const
{
#ifdef _DEBUG
RakAssert( position < Size() );
#endif
//return array[(head + position) % allocation_size];
if ( head + position >= allocation_size )
return array[ head + position - allocation_size ];
else
return array[ head + position ];
}
template <class queue_type>
void Queue<queue_type>::RemoveAtIndex( unsigned int position )
{
#ifdef _DEBUG
RakAssert( position < Size() );
RakAssert( head != tail );
#endif
if ( head == tail || position >= Size() )
return ;
unsigned int index;
unsigned int next;
//index = (head + position) % allocation_size;
if ( head + position >= allocation_size )
index = head + position - allocation_size;
else
index = head + position;
//next = (index + 1) % allocation_size;
next = index + 1;
if ( next == allocation_size )
next = 0;
while ( next != tail )
{
// Overwrite the previous element
array[ index ] = array[ next ];
index = next;
//next = (next + 1) % allocation_size;
if ( ++next == allocation_size )
next = 0;
}
// Move the tail back
if ( tail == 0 )
tail = allocation_size - 1;
else
--tail;
}
} // End namespace
#endif

View File

@@ -0,0 +1,103 @@
/// \file DS_QueueLinkedList.h
/// \internal
/// \brief A queue implemented as a linked list.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __QUEUE_LINKED_LIST_H
#define __QUEUE_LINKED_LIST_H
#include "DS_LinkedList.h"
#include "Export.h"
#include "RakMemoryOverride.h"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
/// \brief A queue implemented using a linked list. Rarely used.
template <class QueueType>
class RAK_DLL_EXPORT QueueLinkedList
{
public:
QueueLinkedList();
QueueLinkedList( const QueueLinkedList& original_copy );
bool operator= ( const QueueLinkedList& original_copy );
QueueType Pop( void );
QueueType& Peek( void );
QueueType& EndPeek( void );
void Push( const QueueType& input );
unsigned int Size( void );
void Clear( void );
void Compress( void );
private:
LinkedList<QueueType> data;
};
template <class QueueType>
QueueLinkedList<QueueType>::QueueLinkedList()
{
}
template <class QueueType>
inline unsigned int QueueLinkedList<QueueType>::Size()
{
return data.Size();
}
template <class QueueType>
inline QueueType QueueLinkedList<QueueType>::Pop( void )
{
data.Beginning();
return ( QueueType ) data.Pop();
}
template <class QueueType>
inline QueueType& QueueLinkedList<QueueType>::Peek( void )
{
data.Beginning();
return ( QueueType ) data.Peek();
}
template <class QueueType>
inline QueueType& QueueLinkedList<QueueType>::EndPeek( void )
{
data.End();
return ( QueueType ) data.Peek();
}
template <class QueueType>
void QueueLinkedList<QueueType>::Push( const QueueType& input )
{
data.End();
data.Add( input );
}
template <class QueueType>
QueueLinkedList<QueueType>::QueueLinkedList( const QueueLinkedList& original_copy )
{
data = original_copy.data;
}
template <class QueueType>
bool QueueLinkedList<QueueType>::operator= ( const QueueLinkedList& original_copy )
{
if ( ( &original_copy ) == this )
return false;
data = original_copy.data;
}
template <class QueueType>
void QueueLinkedList<QueueType>::Clear ( void )
{
data.Clear();
}
} // End namespace
#endif

View File

@@ -0,0 +1,236 @@
/// \file DS_RangeList.h
/// \internal
/// \brief A queue implemented as a linked list.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __RANGE_LIST_H
#define __RANGE_LIST_H
#include "DS_OrderedList.h"
#include "BitStream.h"
#include "RakMemoryOverride.h"
#include "RakAssert.h"
namespace DataStructures
{
template <class range_type>
struct RangeNode
{
RangeNode() {}
~RangeNode() {}
RangeNode(range_type min, range_type max) {minIndex=min; maxIndex=max;}
range_type minIndex;
range_type maxIndex;
};
template <class range_type>
int RangeNodeComp(const range_type &a, const RangeNode<range_type> &b)
{
if (a<b.minIndex)
return -1;
if (a==b.minIndex)
return 0;
return 1;
}
template <class range_type>
class RAK_DLL_EXPORT RangeList
{
public:
RangeList();
~RangeList();
void Insert(range_type index);
void Clear(void);
unsigned Size(void) const;
unsigned RangeSum(void) const;
RakNet::BitSize_t Serialize(RakNet::BitStream *in, RakNet::BitSize_t maxBits, bool clearSerialized);
bool Deserialize(RakNet::BitStream *out);
DataStructures::OrderedList<range_type, RangeNode<range_type> , RangeNodeComp<range_type> > ranges;
};
template <class range_type>
RakNet::BitSize_t RangeList<range_type>::Serialize(RakNet::BitStream *in, RakNet::BitSize_t maxBits, bool clearSerialized)
{
RakAssert(ranges.Size() < (unsigned short)-1);
RakNet::BitStream tempBS;
RakNet::BitSize_t bitsWritten;
unsigned short countWritten;
unsigned i;
countWritten=0;
bitsWritten=0;
for (i=0; i < ranges.Size(); i++)
{
if ((int)sizeof(unsigned short)*8+bitsWritten+(int)sizeof(range_type)*8*2+1>maxBits)
break;
unsigned char minEqualsMax;
if (ranges[i].minIndex==ranges[i].maxIndex)
minEqualsMax=1;
else
minEqualsMax=0;
tempBS.Write(minEqualsMax); // Use one byte, intead of one bit, for speed, as this is done a lot
tempBS.Write(ranges[i].minIndex);
bitsWritten+=sizeof(range_type)*8+8;
if (ranges[i].minIndex!=ranges[i].maxIndex)
{
tempBS.Write(ranges[i].maxIndex);
bitsWritten+=sizeof(range_type)*8;
}
countWritten++;
}
in->AlignWriteToByteBoundary();
RakNet::BitSize_t before=in->GetWriteOffset();
in->Write(countWritten);
bitsWritten+=in->GetWriteOffset()-before;
// RAKNET_DEBUG_PRINTF("%i ", in->GetNumberOfBitsUsed());
in->Write(&tempBS, tempBS.GetNumberOfBitsUsed());
// RAKNET_DEBUG_PRINTF("%i %i \n", tempBS.GetNumberOfBitsUsed(),in->GetNumberOfBitsUsed());
if (clearSerialized && countWritten)
{
unsigned rangeSize=ranges.Size();
for (i=0; i < rangeSize-countWritten; i++)
{
ranges[i]=ranges[i+countWritten];
}
ranges.RemoveFromEnd(countWritten);
}
return bitsWritten;
}
template <class range_type>
bool RangeList<range_type>::Deserialize(RakNet::BitStream *out)
{
ranges.Clear(true, _FILE_AND_LINE_);
unsigned short count;
out->AlignReadToByteBoundary();
out->Read(count);
unsigned short i;
range_type min,max;
unsigned char maxEqualToMin=0;
for (i=0; i < count; i++)
{
out->Read(maxEqualToMin);
if (out->Read(min)==false)
return false;
if (maxEqualToMin==false)
{
if (out->Read(max)==false)
return false;
if (max<min)
return false;
}
else
max=min;
ranges.InsertAtEnd(RangeNode<range_type>(min,max), _FILE_AND_LINE_);
}
return true;
}
template <class range_type>
RangeList<range_type>::RangeList()
{
RangeNodeComp<range_type>(0, RangeNode<range_type>());
}
template <class range_type>
RangeList<range_type>::~RangeList()
{
Clear();
}
template <class range_type>
void RangeList<range_type>::Insert(range_type index)
{
if (ranges.Size()==0)
{
ranges.Insert(index, RangeNode<range_type>(index, index), true, _FILE_AND_LINE_);
return;
}
bool objectExists;
unsigned insertionIndex=ranges.GetIndexFromKey(index, &objectExists);
if (insertionIndex==ranges.Size())
{
if (index == ranges[insertionIndex-1].maxIndex+(range_type)1)
ranges[insertionIndex-1].maxIndex++;
else if (index > ranges[insertionIndex-1].maxIndex+(range_type)1)
{
// Insert at end
ranges.Insert(index, RangeNode<range_type>(index, index), true, _FILE_AND_LINE_);
}
return;
}
if (index < ranges[insertionIndex].minIndex-(range_type)1)
{
// Insert here
ranges.InsertAtIndex(RangeNode<range_type>(index, index), insertionIndex, _FILE_AND_LINE_);
return;
}
else if (index == ranges[insertionIndex].minIndex-(range_type)1)
{
// Decrease minIndex and join left
ranges[insertionIndex].minIndex--;
if (insertionIndex>0 && ranges[insertionIndex-1].maxIndex+(range_type)1==ranges[insertionIndex].minIndex)
{
ranges[insertionIndex-1].maxIndex=ranges[insertionIndex].maxIndex;
ranges.RemoveAtIndex(insertionIndex);
}
return;
}
else if (index >= ranges[insertionIndex].minIndex && index <= ranges[insertionIndex].maxIndex)
{
// Already exists
return;
}
else if (index == ranges[insertionIndex].maxIndex+(range_type)1)
{
// Increase maxIndex and join right
ranges[insertionIndex].maxIndex++;
if (insertionIndex<ranges.Size()-1 && ranges[insertionIndex+(range_type)1].minIndex==ranges[insertionIndex].maxIndex+(range_type)1)
{
ranges[insertionIndex+1].minIndex=ranges[insertionIndex].minIndex;
ranges.RemoveAtIndex(insertionIndex);
}
return;
}
}
template <class range_type>
void RangeList<range_type>::Clear(void)
{
ranges.Clear(true, _FILE_AND_LINE_);
}
template <class range_type>
unsigned RangeList<range_type>::Size(void) const
{
return ranges.Size();
}
template <class range_type>
unsigned RangeList<range_type>::RangeSum(void) const
{
unsigned sum=0,i;
for (i=0; i < ranges.Size(); i++)
sum+=ranges[i].maxIndex-ranges[i].minIndex+1;
return sum;
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,343 @@
/// \file DS_Table.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __TABLE_H
#define __TABLE_H
#ifdef _MSC_VER
#pragma warning( push )
#endif
#include "DS_List.h"
#include "DS_BPlusTree.h"
#include "RakMemoryOverride.h"
#include "Export.h"
#include "RakString.h"
#define _TABLE_BPLUS_TREE_ORDER 16
#define _TABLE_MAX_COLUMN_NAME_LENGTH 64
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
/// \brief Holds a set of columns, a set of rows, and rows times columns cells.
/// \details The table data structure is useful if you want to store a set of structures and perform queries on those structures.<BR>
/// This is a relatively simple and fast implementation of the types of tables commonly used in databases.<BR>
/// See TableSerializer to serialize data members of the table.<BR>
/// See LightweightDatabaseClient and LightweightDatabaseServer to transmit the table over the network.
class RAK_DLL_EXPORT Table
{
public:
enum ColumnType
{
// Cell::i used
NUMERIC,
// Cell::c used to hold a null terminated string.
STRING,
// Cell::c holds data. Cell::i holds data length of c in bytes.
BINARY,
// Cell::c holds data. Not deallocated. Set manually by assigning ptr.
POINTER,
};
/// Holds the actual data in the table
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct RAK_DLL_EXPORT Cell
{
Cell();
~Cell();
Cell(double numericValue, char *charValue, void *ptr, ColumnType type);
void SetByType(double numericValue, char *charValue, void *ptr, ColumnType type);
void Clear(void);
/// Numeric
void Set(int input);
void Set(unsigned int input);
void Set(double input);
/// String
void Set(const char *input);
/// Binary
void Set(const char *input, int inputLength);
/// Pointer
void SetPtr(void* p);
/// Numeric
void Get(int *output);
void Get(double *output);
/// String
void Get(char *output);
/// Binary
void Get(char *output, int *outputLength);
RakNet::RakString ToString(ColumnType columnType);
// assignment operator and copy constructor
Cell& operator = ( const Cell& input );
Cell( const Cell & input);
ColumnType EstimateColumnType(void) const;
bool isEmpty;
double i;
char *c;
void *ptr;
};
/// Stores the name and type of the column
/// \internal
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct RAK_DLL_EXPORT ColumnDescriptor
{
ColumnDescriptor();
~ColumnDescriptor();
ColumnDescriptor(const char cn[_TABLE_MAX_COLUMN_NAME_LENGTH],ColumnType ct);
char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH];
ColumnType columnType;
};
/// Stores the list of cells for this row, and a special flag used for internal sorting
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct RAK_DLL_EXPORT Row
{
// list of cells
DataStructures::List<Cell*> cells;
/// Numeric
void UpdateCell(unsigned columnIndex, double value);
/// String
void UpdateCell(unsigned columnIndex, const char *str);
/// Binary
void UpdateCell(unsigned columnIndex, int byteLength, const char *data);
};
// Operations to perform for cell comparison
enum FilterQueryType
{
QF_EQUAL,
QF_NOT_EQUAL,
QF_GREATER_THAN,
QF_GREATER_THAN_EQ,
QF_LESS_THAN,
QF_LESS_THAN_EQ,
QF_IS_EMPTY,
QF_NOT_EMPTY,
};
// Compare the cell value for a row at columnName to the cellValue using operation.
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct RAK_DLL_EXPORT FilterQuery
{
FilterQuery();
~FilterQuery();
FilterQuery(unsigned column, Cell *cell, FilterQueryType op);
// If columnName is specified, columnIndex will be looked up using it.
char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH];
unsigned columnIndex;
Cell *cellValue;
FilterQueryType operation;
};
/// Increasing or decreasing sort order
enum SortQueryType
{
QS_INCREASING_ORDER,
QS_DECREASING_ORDER,
};
// Sort on increasing or decreasing order for a particular column
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct RAK_DLL_EXPORT SortQuery
{
/// The index of the table column we are sorting on
unsigned columnIndex;
/// See SortQueryType
SortQueryType operation;
};
// Constructor
Table();
// Destructor
~Table();
/// \brief Adds a column to the table
/// \param[in] columnName The name of the column
/// \param[in] columnType What type of data this column will hold
/// \return The index of the new column
unsigned AddColumn(const char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH], ColumnType columnType);
/// \brief Removes a column by index
/// \param[in] columnIndex The index of the column to remove
void RemoveColumn(unsigned columnIndex);
/// \brief Gets the index of a column by name
/// \details Column indices are stored in the order they are added.
/// \param[in] columnName The name of the column
/// \return The index of the column, or (unsigned)-1 if no such column
unsigned ColumnIndex(char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]) const;
unsigned ColumnIndex(const char *columnName) const;
/// \brief Gives the string name of the column at a certain index
/// \param[in] index The index of the column
/// \return The name of the column, or 0 if an invalid index
char* ColumnName(unsigned index) const;
/// \brief Returns the type of a column, referenced by index
/// \param[in] index The index of the column
/// \return The type of the column
ColumnType GetColumnType(unsigned index) const;
/// Returns the number of columns
/// \return The number of columns in the table
unsigned GetColumnCount(void) const;
/// Returns the number of rows
/// \return The number of rows in the table
unsigned GetRowCount(void) const;
/// \brief Adds a row to the table
/// \details New rows are added with empty values for all cells. However, if you specify initialCelLValues you can specify initial values
/// It's up to you to ensure that the values in the specific cells match the type of data used by that row
/// rowId can be considered the primary key for the row. It is much faster to lookup a row by its rowId than by searching keys.
/// rowId must be unique
/// Rows are stored in sorted order in the table, using rowId as the sort key
/// \param[in] rowId The UNIQUE primary key for the row. This can never be changed.
/// \param[in] initialCellValues Initial values to give the row (optional)
/// \return The newly added row
Table::Row* AddRow(unsigned rowId);
Table::Row* AddRow(unsigned rowId, DataStructures::List<Cell> &initialCellValues);
Table::Row* AddRow(unsigned rowId, DataStructures::List<Cell*> &initialCellValues, bool copyCells=false);
/// \brief Removes a row specified by rowId.
/// \param[in] rowId The ID of the row
/// \return true if the row was deleted. False if not.
bool RemoveRow(unsigned rowId);
/// \brief Removes all the rows with IDs that the specified table also has.
/// \param[in] tableContainingRowIDs The IDs of the rows
void RemoveRows(Table *tableContainingRowIDs);
/// \brief Updates a particular cell in the table.
/// \note If you are going to update many cells of a particular row, it is more efficient to call GetRow and perform the operations on the row directly.
/// \note Row pointers do not change, so you can also write directly to the rows for more efficiency.
/// \param[in] rowId The ID of the row
/// \param[in] columnIndex The column of the cell
/// \param[in] value The data to set
bool UpdateCell(unsigned rowId, unsigned columnIndex, int value);
bool UpdateCell(unsigned rowId, unsigned columnIndex, char *str);
bool UpdateCell(unsigned rowId, unsigned columnIndex, int byteLength, char *data);
bool UpdateCellByIndex(unsigned rowIndex, unsigned columnIndex, int value);
bool UpdateCellByIndex(unsigned rowIndex, unsigned columnIndex, char *str);
bool UpdateCellByIndex(unsigned rowIndex, unsigned columnIndex, int byteLength, char *data);
/// \brief Note this is much less efficient to call than GetRow, then working with the cells directly.
/// Numeric, string, binary
void GetCellValueByIndex(unsigned rowIndex, unsigned columnIndex, int *output);
void GetCellValueByIndex(unsigned rowIndex, unsigned columnIndex, char *output);
void GetCellValueByIndex(unsigned rowIndex, unsigned columnIndex, char *output, int *outputLength);
/// \brief Gets a row. More efficient to do this and access Row::cells than to repeatedly call GetCell.
/// You can also update cells in rows from this function.
/// \param[in] rowId The ID of the row
/// \return The desired row, or 0 if no such row.
Row* GetRowByID(unsigned rowId) const;
/// \brief Gets a row at a specific index.
/// rowIndex should be less than GetRowCount()
/// \param[in] rowIndex The index of the row
/// \param[out] key The ID of the row returned
/// \return The desired row, or 0 if no such row.
Row* GetRowByIndex(unsigned rowIndex, unsigned *key) const;
/// \brief Queries the table, optionally returning only a subset of columns and rows.
/// \param[in] columnSubset An array of column indices. Only columns in this array are returned. Pass 0 for all columns
/// \param[in] numColumnSubset The number of elements in \a columnSubset
/// \param[in] inclusionFilters An array of FilterQuery. All filters must pass for the row to be returned.
/// \param[in] numInclusionFilters The number of elements in \a inclusionFilters
/// \param[in] rowIds An arrow of row IDs. Only these rows with these IDs are returned. Pass 0 for all rows.
/// \param[in] numRowIDs The number of elements in \a rowIds
/// \param[out] result The result of the query. If no rows are returned, the table will only have columns.
void QueryTable(unsigned *columnIndicesSubset, unsigned numColumnSubset, FilterQuery *inclusionFilters, unsigned numInclusionFilters, unsigned *rowIds, unsigned numRowIDs, Table *result);
/// \brief Sorts the table by rows
/// \details You can sort the table in ascending or descending order on one or more columns
/// Columns have precedence in the order they appear in the \a sortQueries array
/// If a row cell on column n has the same value as a a different row on column n, then the row will be compared on column n+1
/// \param[in] sortQueries A list of SortQuery structures, defining the sorts to perform on the table
/// \param[in] numColumnSubset The number of elements in \a numSortQueries
/// \param[out] out The address of an array of Rows, which will receive the sorted output. The array must be long enough to contain all returned rows, up to GetRowCount()
void SortTable(Table::SortQuery *sortQueries, unsigned numSortQueries, Table::Row** out);
/// \brief Frees all memory in the table.
void Clear(void);
/// \brief Prints out the names of all the columns.
/// \param[out] out A pointer to an array of bytes which will hold the output.
/// \param[in] outLength The size of the \a out array
/// \param[in] columnDelineator What character to print to delineate columns
void PrintColumnHeaders(char *out, int outLength, char columnDelineator) const;
/// \brief Writes a text representation of the row to \a out.
/// \param[out] out A pointer to an array of bytes which will hold the output.
/// \param[in] outLength The size of the \a out array
/// \param[in] columnDelineator What character to print to delineate columns
/// \param[in] printDelineatorForBinary Binary output is not printed. True to still print the delineator.
/// \param[in] inputRow The row to print
void PrintRow(char *out, int outLength, char columnDelineator, bool printDelineatorForBinary, Table::Row* inputRow) const;
/// \brief Direct access to make things easier.
const DataStructures::List<ColumnDescriptor>& GetColumns(void) const;
/// \brief Direct access to make things easier.
const DataStructures::BPlusTree<unsigned, Row*, _TABLE_BPLUS_TREE_ORDER>& GetRows(void) const;
/// \brief Get the head of a linked list containing all the row data.
DataStructures::Page<unsigned, Row*, _TABLE_BPLUS_TREE_ORDER> * GetListHead(void);
/// \brief Get the first free row id.
/// This could be made more efficient.
unsigned GetAvailableRowId(void) const;
Table& operator = ( const Table& input );
protected:
Table::Row* AddRowColumns(unsigned rowId, Row *row, DataStructures::List<unsigned> columnIndices);
void DeleteRow(Row *row);
void QueryRow(DataStructures::List<unsigned> &inclusionFilterColumnIndices, DataStructures::List<unsigned> &columnIndicesToReturn, unsigned key, Table::Row* row, FilterQuery *inclusionFilters, Table *result);
// 16 is arbitrary and is the order of the BPlus tree. Higher orders are better for searching while lower orders are better for
// Insertions and deletions.
DataStructures::BPlusTree<unsigned, Row*, _TABLE_BPLUS_TREE_ORDER> rows;
// Columns in the table.
DataStructures::List<ColumnDescriptor> columns;
};
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif

View File

@@ -0,0 +1,143 @@
/// \file DS_ThreadsafeAllocatingQueue.h
/// \internal
/// A threadsafe queue, that also uses a memory pool for allocation
#ifndef __THREADSAFE_ALLOCATING_QUEUE
#define __THREADSAFE_ALLOCATING_QUEUE
#include "DS_Queue.h"
#include "SimpleMutex.h"
#include "DS_MemoryPool.h"
// #if defined(new)
// #pragma push_macro("new")
// #undef new
// #define RMO_NEW_UNDEF_ALLOCATING_QUEUE
// #endif
namespace DataStructures
{
template <class structureType>
class RAK_DLL_EXPORT ThreadsafeAllocatingQueue
{
public:
// Queue operations
void Push(structureType *s);
structureType *PopInaccurate(void);
structureType *Pop(void);
void SetPageSize(int size);
bool IsEmpty(void);
// Memory pool operations
structureType *Allocate(const char *file, unsigned int line);
void Deallocate(structureType *s, const char *file, unsigned int line);
void Clear(const char *file, unsigned int line);
protected:
MemoryPool<structureType> memoryPool;
RakNet::SimpleMutex memoryPoolMutex;
Queue<structureType*> queue;
RakNet::SimpleMutex queueMutex;
};
template <class structureType>
void ThreadsafeAllocatingQueue<structureType>::Push(structureType *s)
{
queueMutex.Lock();
queue.Push(s, _FILE_AND_LINE_ );
queueMutex.Unlock();
}
template <class structureType>
structureType *ThreadsafeAllocatingQueue<structureType>::PopInaccurate(void)
{
structureType *s;
if (queue.IsEmpty())
return 0;
queueMutex.Lock();
if (queue.IsEmpty()==false)
s=queue.Pop();
else
s=0;
queueMutex.Unlock();
return s;
}
template <class structureType>
structureType *ThreadsafeAllocatingQueue<structureType>::Pop(void)
{
structureType *s;
queueMutex.Lock();
if (queue.IsEmpty())
{
queueMutex.Unlock();
return 0;
}
s=queue.Pop();
queueMutex.Unlock();
return s;
}
template <class structureType>
structureType *ThreadsafeAllocatingQueue<structureType>::Allocate(const char *file, unsigned int line)
{
structureType *s;
memoryPoolMutex.Lock();
s=memoryPool.Allocate(file, line);
memoryPoolMutex.Unlock();
// Call new operator, memoryPool doesn't do this
s = new ((void*)s) structureType;
return s;
}
template <class structureType>
void ThreadsafeAllocatingQueue<structureType>::Deallocate(structureType *s, const char *file, unsigned int line)
{
// Call delete operator, memory pool doesn't do this
s->~structureType();
memoryPoolMutex.Lock();
memoryPool.Release(s, file, line);
memoryPoolMutex.Unlock();
}
template <class structureType>
void ThreadsafeAllocatingQueue<structureType>::Clear(const char *file, unsigned int line)
{
memoryPoolMutex.Lock();
for (unsigned int i=0; i < queue.Size(); i++)
{
queue[i]->~structureType();
memoryPool.Release(queue[i], file, line);
}
queue.Clear(file, line);
memoryPoolMutex.Unlock();
memoryPoolMutex.Lock();
memoryPool.Clear(file, line);
memoryPoolMutex.Unlock();
}
template <class structureType>
void ThreadsafeAllocatingQueue<structureType>::SetPageSize(int size)
{
memoryPool.SetPageSize(size);
}
template <class structureType>
bool ThreadsafeAllocatingQueue<structureType>::IsEmpty(void)
{
bool isEmpty;
queueMutex.Lock();
isEmpty=queue.IsEmpty();
queueMutex.Unlock();
return isEmpty;
}
};
// #if defined(RMO_NEW_UNDEF_ALLOCATING_QUEUE)
// #pragma pop_macro("new")
// #undef RMO_NEW_UNDEF_ALLOCATING_QUEUE
// #endif
#endif

View File

@@ -0,0 +1,98 @@
/// \file DS_Tree.h
/// \internal
/// \brief Just a regular tree
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __DS_TREE_H
#define __DS_TREE_H
#include "Export.h"
#include "DS_List.h"
#include "DS_Queue.h"
#include "RakMemoryOverride.h"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
template <class TreeType>
class RAK_DLL_EXPORT Tree
{
public:
Tree();
Tree(TreeType &inputData);
~Tree();
void LevelOrderTraversal(DataStructures::List<Tree*> &output);
void AddChild(TreeType &newData);
void DeleteDecendants(void);
TreeType data;
DataStructures::List<Tree *> children;
};
template <class TreeType>
Tree<TreeType>::Tree()
{
}
template <class TreeType>
Tree<TreeType>::Tree(TreeType &inputData)
{
data=inputData;
}
template <class TreeType>
Tree<TreeType>::~Tree()
{
DeleteDecendants();
}
template <class TreeType>
void Tree<TreeType>::LevelOrderTraversal(DataStructures::List<Tree*> &output)
{
unsigned i;
Tree<TreeType> *node;
DataStructures::Queue<Tree<TreeType>*> queue;
for (i=0; i < children.Size(); i++)
queue.Push(children[i]);
while (queue.Size())
{
node=queue.Pop();
output.Insert(node, _FILE_AND_LINE_);
for (i=0; i < node->children.Size(); i++)
queue.Push(node->children[i]);
}
}
template <class TreeType>
void Tree<TreeType>::AddChild(TreeType &newData)
{
children.Insert(RakNet::OP_NEW<Tree>(newData, _FILE_AND_LINE_));
}
template <class TreeType>
void Tree<TreeType>::DeleteDecendants(void)
{
/*
DataStructures::List<Tree*> output;
LevelOrderTraversal(output);
unsigned i;
for (i=0; i < output.Size(); i++)
RakNet::OP_DELETE(output[i], _FILE_AND_LINE_);
*/
// Already recursive to do this
unsigned int i;
for (i=0; i < children.Size(); i++)
RakNet::OP_DELETE(children[i], _FILE_AND_LINE_);
}
}
#endif

View File

@@ -0,0 +1,537 @@
/// \file DS_WeightedGraph.h
/// \internal
/// \brief Weighted graph.
/// \details I'm assuming the indices are complex map types, rather than sequential numbers (which could be implemented much more efficiently).
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __WEIGHTED_GRAPH_H
#define __WEIGHTED_GRAPH_H
#include "DS_OrderedList.h"
#include "DS_Map.h"
#include "DS_Heap.h"
#include "DS_Queue.h"
#include "DS_Tree.h"
#include "RakAssert.h"
#include "RakMemoryOverride.h"
#ifdef _DEBUG
#include <stdio.h>
#endif
#ifdef _MSC_VER
#pragma warning( push )
#endif
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
template <class node_type, class weight_type, bool allow_unlinkedNodes>
class RAK_DLL_EXPORT WeightedGraph
{
public:
static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison<node_type>(node_type(),node_type());}
WeightedGraph();
~WeightedGraph();
WeightedGraph( const WeightedGraph& original_copy );
WeightedGraph& operator= ( const WeightedGraph& original_copy );
void AddNode(const node_type &node);
void RemoveNode(const node_type &node);
void AddConnection(const node_type &node1, const node_type &node2, weight_type weight);
void RemoveConnection(const node_type &node1, const node_type &node2);
bool HasConnection(const node_type &node1, const node_type &node2);
void Print(void);
void Clear(void);
bool GetShortestPath(DataStructures::List<node_type> &path, node_type startNode, node_type endNode, weight_type INFINITE_WEIGHT);
bool GetSpanningTree(DataStructures::Tree<node_type> &outTree, DataStructures::List<node_type> *inputNodes, node_type startNode, weight_type INFINITE_WEIGHT );
unsigned GetNodeCount(void) const;
unsigned GetConnectionCount(unsigned nodeIndex) const;
void GetConnectionAtIndex(unsigned nodeIndex, unsigned connectionIndex, node_type &outNode, weight_type &outWeight) const;
node_type GetNodeAtIndex(unsigned nodeIndex) const;
protected:
void ClearDijkstra(void);
void GenerateDisjktraMatrix(node_type startNode, weight_type INFINITE_WEIGHT);
DataStructures::Map<node_type, DataStructures::Map<node_type, weight_type> *> adjacencyLists;
// All these variables are for path finding with Dijkstra
// 08/23/06 Won't compile as a DLL inside this struct
// struct
// {
bool isValidPath;
node_type rootNode;
DataStructures::OrderedList<node_type, node_type> costMatrixIndices;
weight_type *costMatrix;
node_type *leastNodeArray;
// } dijkstra;
struct NodeAndParent
{
DataStructures::Tree<node_type>*node;
DataStructures::Tree<node_type>*parent;
};
};
template <class node_type, class weight_type, bool allow_unlinkedNodes>
WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::WeightedGraph()
{
isValidPath=false;
costMatrix=0;
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::~WeightedGraph()
{
Clear();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::WeightedGraph( const WeightedGraph& original_copy )
{
adjacencyLists=original_copy.adjacencyLists;
isValidPath=original_copy.isValidPath;
if (isValidPath)
{
rootNode=original_copy.rootNode;
costMatrixIndices=original_copy.costMatrixIndices;
costMatrix = RakNet::OP_NEW_ARRAY<weight_type>(costMatrixIndices.Size() * costMatrixIndices.Size(), _FILE_AND_LINE_ );
leastNodeArray = RakNet::OP_NEW_ARRAY<node_type>(costMatrixIndices.Size(), _FILE_AND_LINE_ );
memcpy(costMatrix, original_copy.costMatrix, costMatrixIndices.Size() * costMatrixIndices.Size() * sizeof(weight_type));
memcpy(leastNodeArray, original_copy.leastNodeArray, costMatrixIndices.Size() * sizeof(weight_type));
}
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
WeightedGraph<node_type, weight_type, allow_unlinkedNodes>& WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::operator=( const WeightedGraph& original_copy )
{
adjacencyLists=original_copy.adjacencyLists;
isValidPath=original_copy.isValidPath;
if (isValidPath)
{
rootNode=original_copy.rootNode;
costMatrixIndices=original_copy.costMatrixIndices;
costMatrix = RakNet::OP_NEW_ARRAY<weight_type>(costMatrixIndices.Size() * costMatrixIndices.Size(), _FILE_AND_LINE_ );
leastNodeArray = RakNet::OP_NEW_ARRAY<node_type>(costMatrixIndices.Size(), _FILE_AND_LINE_ );
memcpy(costMatrix, original_copy.costMatrix, costMatrixIndices.Size() * costMatrixIndices.Size() * sizeof(weight_type));
memcpy(leastNodeArray, original_copy.leastNodeArray, costMatrixIndices.Size() * sizeof(weight_type));
}
return *this;
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::AddNode(const node_type &node)
{
adjacencyLists.SetNew(node, RakNet::OP_NEW<DataStructures::Map<node_type, weight_type> >( _FILE_AND_LINE_) );
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::RemoveNode(const node_type &node)
{
unsigned i;
DataStructures::Queue<node_type> removeNodeQueue;
removeNodeQueue.Push(node, _FILE_AND_LINE_ );
while (removeNodeQueue.Size())
{
RakNet::OP_DELETE(adjacencyLists.Pop(removeNodeQueue.Pop()), _FILE_AND_LINE_);
// Remove this node from all of the other lists as well
for (i=0; i < adjacencyLists.Size(); i++)
{
adjacencyLists[i]->Delete(node);
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
if (allow_unlinkedNodes==false && adjacencyLists[i]->Size()==0)
removeNodeQueue.Push(adjacencyLists.GetKeyAtIndex(i), _FILE_AND_LINE_ );
}
}
ClearDijkstra();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
bool WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::HasConnection(const node_type &node1, const node_type &node2)
{
if (node1==node2)
return false;
if (adjacencyLists.Has(node1)==false)
return false;
return adjacencyLists.Get(node1)->Has(node2);
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::AddConnection(const node_type &node1, const node_type &node2, weight_type weight)
{
if (node1==node2)
return;
if (adjacencyLists.Has(node1)==false)
AddNode(node1);
adjacencyLists.Get(node1)->Set(node2, weight);
if (adjacencyLists.Has(node2)==false)
AddNode(node2);
adjacencyLists.Get(node2)->Set(node1, weight);
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::RemoveConnection(const node_type &node1, const node_type &node2)
{
adjacencyLists.Get(node2)->Delete(node1);
adjacencyLists.Get(node1)->Delete(node2);
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
if (allow_unlinkedNodes==false) // If we do not allow _unlinked nodes, then if there are no connections, remove the node
{
if (adjacencyLists.Get(node1)->Size()==0)
RemoveNode(node1); // Will also remove node1 from the adjacency list of node2
if (adjacencyLists.Has(node2) && adjacencyLists.Get(node2)->Size()==0)
RemoveNode(node2);
}
ClearDijkstra();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::Clear(void)
{
unsigned i;
for (i=0; i < adjacencyLists.Size(); i++)
RakNet::OP_DELETE(adjacencyLists[i], _FILE_AND_LINE_);
adjacencyLists.Clear();
ClearDijkstra();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
bool WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetShortestPath(DataStructures::List<node_type> &path, node_type startNode, node_type endNode, weight_type INFINITE_WEIGHT)
{
path.Clear(false, _FILE_AND_LINE_);
if (startNode==endNode)
{
path.Insert(startNode, _FILE_AND_LINE_);
path.Insert(endNode, _FILE_AND_LINE_);
return true;
}
if (isValidPath==false || rootNode!=startNode)
{
ClearDijkstra();
GenerateDisjktraMatrix(startNode, INFINITE_WEIGHT);
}
// return the results
bool objectExists;
unsigned col,row;
weight_type currentWeight;
DataStructures::Queue<node_type> outputQueue;
col=costMatrixIndices.GetIndexFromKey(endNode, &objectExists);
if (costMatrixIndices.Size()<2)
{
return false;
}
if (objectExists==false)
{
return false;
}
node_type vertex;
row=costMatrixIndices.Size()-2;
if (row==0)
{
path.Insert(startNode, _FILE_AND_LINE_);
path.Insert(endNode, _FILE_AND_LINE_);
return true;
}
currentWeight=costMatrix[row*adjacencyLists.Size() + col];
if (currentWeight==INFINITE_WEIGHT)
{
// No path
return true;
}
vertex=endNode;
outputQueue.PushAtHead(vertex, 0, _FILE_AND_LINE_);
row--;
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while (1)
{
while (costMatrix[row*adjacencyLists.Size() + col] == currentWeight)
{
if (row==0)
{
path.Insert(startNode, _FILE_AND_LINE_);
for (col=0; outputQueue.Size(); col++)
path.Insert(outputQueue.Pop(), _FILE_AND_LINE_);
return true;
}
--row;
}
vertex=leastNodeArray[row];
outputQueue.PushAtHead(vertex, 0, _FILE_AND_LINE_);
if (row==0)
break;
col=costMatrixIndices.GetIndexFromKey(vertex, &objectExists);
currentWeight=costMatrix[row*adjacencyLists.Size() + col];
}
path.Insert(startNode, _FILE_AND_LINE_);
for (col=0; outputQueue.Size(); col++)
path.Insert(outputQueue.Pop(), _FILE_AND_LINE_);
return true;
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
node_type WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetNodeAtIndex(unsigned nodeIndex) const
{
return adjacencyLists.GetKeyAtIndex(nodeIndex);
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
unsigned WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetNodeCount(void) const
{
return adjacencyLists.Size();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
unsigned WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetConnectionCount(unsigned nodeIndex) const
{
return adjacencyLists[nodeIndex]->Size();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetConnectionAtIndex(unsigned nodeIndex, unsigned connectionIndex, node_type &outNode, weight_type &outWeight) const
{
outWeight=adjacencyLists[nodeIndex]->operator[](connectionIndex);
outNode=adjacencyLists[nodeIndex]->GetKeyAtIndex(connectionIndex);
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
bool WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetSpanningTree(DataStructures::Tree<node_type> &outTree, DataStructures::List<node_type> *inputNodes, node_type startNode, weight_type INFINITE_WEIGHT )
{
// Find the shortest path from the start node to each of the input nodes. Add this path to a new WeightedGraph if the result is reachable
DataStructures::List<node_type> path;
DataStructures::WeightedGraph<node_type, weight_type, allow_unlinkedNodes> outGraph;
bool res;
unsigned i,j;
for (i=0; i < inputNodes->Size(); i++)
{
res=GetShortestPath(path, startNode, (*inputNodes)[i], INFINITE_WEIGHT);
if (res && path.Size()>0)
{
for (j=0; j < path.Size()-1; j++)
{
// Don't bother looking up the weight
outGraph.AddConnection(path[j], path[j+1], INFINITE_WEIGHT);
}
}
}
// Copy the graph to a tree.
DataStructures::Queue<NodeAndParent> nodesToProcess;
DataStructures::Tree<node_type> *current;
DataStructures::Map<node_type, weight_type> *adjacencyList;
node_type key;
NodeAndParent nap, nap2;
outTree.DeleteDecendants();
outTree.data=startNode;
current=&outTree;
if (outGraph.adjacencyLists.Has(startNode)==false)
return false;
adjacencyList = outGraph.adjacencyLists.Get(startNode);
for (i=0; i < adjacencyList->Size(); i++)
{
nap2.node=RakNet::OP_NEW<DataStructures::Tree<node_type> >( _FILE_AND_LINE_ );
nap2.node->data=adjacencyList->GetKeyAtIndex(i);
nap2.parent=current;
nodesToProcess.Push(nap2, _FILE_AND_LINE_ );
current->children.Insert(nap2.node, _FILE_AND_LINE_);
}
while (nodesToProcess.Size())
{
nap=nodesToProcess.Pop();
current=nap.node;
adjacencyList = outGraph.adjacencyLists.Get(nap.node->data);
for (i=0; i < adjacencyList->Size(); i++)
{
key=adjacencyList->GetKeyAtIndex(i);
if (key!=nap.parent->data)
{
nap2.node=RakNet::OP_NEW<DataStructures::Tree<node_type> >( _FILE_AND_LINE_ );
nap2.node->data=key;
nap2.parent=current;
nodesToProcess.Push(nap2, _FILE_AND_LINE_ );
current->children.Insert(nap2.node, _FILE_AND_LINE_);
}
}
}
return true;
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GenerateDisjktraMatrix(node_type startNode, weight_type INFINITE_WEIGHT)
{
if (adjacencyLists.Size()==0)
return;
costMatrix = RakNet::OP_NEW_ARRAY<weight_type>(adjacencyLists.Size() * adjacencyLists.Size(), _FILE_AND_LINE_ );
leastNodeArray = RakNet::OP_NEW_ARRAY<node_type>(adjacencyLists.Size(), _FILE_AND_LINE_ );
node_type currentNode;
unsigned col, row, row2, openSetIndex;
node_type adjacentKey;
unsigned adjacentIndex;
weight_type edgeWeight, currentNodeWeight, adjacentNodeWeight;
DataStructures::Map<node_type, weight_type> *adjacencyList;
DataStructures::Heap<weight_type, node_type, false> minHeap;
DataStructures::Map<node_type, weight_type> openSet;
for (col=0; col < adjacencyLists.Size(); col++)
{
// This should be already sorted, so it's a bit inefficient to do an insertion sort, but what the heck
costMatrixIndices.Insert(adjacencyLists.GetKeyAtIndex(col),adjacencyLists.GetKeyAtIndex(col), true, _FILE_AND_LINE_);
}
for (col=0; col < adjacencyLists.Size() * adjacencyLists.Size(); col++)
costMatrix[col]=INFINITE_WEIGHT;
currentNode=startNode;
row=0;
currentNodeWeight=0;
rootNode=startNode;
// Clear the starting node column
if (adjacencyLists.Size())
{
adjacentIndex=adjacencyLists.GetIndexAtKey(startNode);
for (row2=0; row2 < adjacencyLists.Size(); row2++)
costMatrix[row2*adjacencyLists.Size() + adjacentIndex]=0;
}
while (row < adjacencyLists.Size()-1)
{
adjacencyList = adjacencyLists.Get(currentNode);
// Go through all connections from the current node. If the new weight is less than the current weight, then update that weight.
for (col=0; col < adjacencyList->Size(); col++)
{
edgeWeight=(*adjacencyList)[col];
adjacentKey=adjacencyList->GetKeyAtIndex(col);
adjacentIndex=adjacencyLists.GetIndexAtKey(adjacentKey);
adjacentNodeWeight=costMatrix[row*adjacencyLists.Size() + adjacentIndex];
if (currentNodeWeight + edgeWeight < adjacentNodeWeight)
{
// Update the weight for the adjacent node
for (row2=row; row2 < adjacencyLists.Size(); row2++)
costMatrix[row2*adjacencyLists.Size() + adjacentIndex]=currentNodeWeight + edgeWeight;
openSet.Set(adjacentKey, currentNodeWeight + edgeWeight);
}
}
// Find the lowest in the open set
minHeap.Clear(true,_FILE_AND_LINE_);
for (openSetIndex=0; openSetIndex < openSet.Size(); openSetIndex++)
minHeap.Push(openSet[openSetIndex], openSet.GetKeyAtIndex(openSetIndex),_FILE_AND_LINE_);
/*
unsigned i,j;
for (i=0; i < adjacencyLists.Size()-1; i++)
{
for (j=0; j < adjacencyLists.Size(); j++)
{
RAKNET_DEBUG_PRINTF("%2i ", costMatrix[i*adjacencyLists.Size() + j]);
}
RAKNET_DEBUG_PRINTF("Node=%i", leastNodeArray[i]);
RAKNET_DEBUG_PRINTF("\n");
}
*/
if (minHeap.Size()==0)
{
// Unreachable nodes
isValidPath=true;
return;
}
currentNodeWeight=minHeap.PeekWeight(0);
leastNodeArray[row]=minHeap.Pop(0);
currentNode=leastNodeArray[row];
openSet.Delete(currentNode);
row++;
}
/*
#ifdef _DEBUG
unsigned i,j;
for (i=0; i < adjacencyLists.Size()-1; i++)
{
for (j=0; j < adjacencyLists.Size(); j++)
{
RAKNET_DEBUG_PRINTF("%2i ", costMatrix[i*adjacencyLists.Size() + j]);
}
RAKNET_DEBUG_PRINTF("Node=%i", leastNodeArray[i]);
RAKNET_DEBUG_PRINTF("\n");
}
#endif
*/
isValidPath=true;
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::ClearDijkstra(void)
{
if (isValidPath)
{
isValidPath=false;
RakNet::OP_DELETE_ARRAY(costMatrix, _FILE_AND_LINE_);
RakNet::OP_DELETE_ARRAY(leastNodeArray, _FILE_AND_LINE_);
costMatrixIndices.Clear(false, _FILE_AND_LINE_);
}
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::Print(void)
{
#ifdef _DEBUG
unsigned i,j;
for (i=0; i < adjacencyLists.Size(); i++)
{
//RAKNET_DEBUG_PRINTF("%i connected to ", i);
RAKNET_DEBUG_PRINTF("%s connected to ", adjacencyLists.GetKeyAtIndex(i).systemAddress.ToString());
if (adjacencyLists[i]->Size()==0)
RAKNET_DEBUG_PRINTF("<Empty>");
else
{
for (j=0; j < adjacencyLists[i]->Size(); j++)
// RAKNET_DEBUG_PRINTF("%i (%.2f) ", adjacencyLists.GetIndexAtKey(adjacencyLists[i]->GetKeyAtIndex(j)), (float) adjacencyLists[i]->operator[](j) );
RAKNET_DEBUG_PRINTF("%s (%.2f) ", adjacencyLists[i]->GetKeyAtIndex(j).systemAddress.ToString(), (float) adjacencyLists[i]->operator[](j) );
}
RAKNET_DEBUG_PRINTF("\n");
}
#endif
}
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif

View File

@@ -0,0 +1,63 @@
#include "DataCompressor.h"
#include "DS_HuffmanEncodingTree.h"
#include "RakAssert.h"
#include <string.h> // Use string.h rather than memory.h for a console
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(DataCompressor,DataCompressor)
void DataCompressor::Compress( unsigned char *userData, unsigned sizeInBytes, RakNet::BitStream * output )
{
// Don't use this for small files as you will just make them bigger!
RakAssert(sizeInBytes > 2048);
unsigned int frequencyTable[ 256 ];
unsigned int i;
memset(frequencyTable,0,256*sizeof(unsigned int));
for (i=0; i < sizeInBytes; i++)
++frequencyTable[userData[i]];
HuffmanEncodingTree tree;
BitSize_t writeOffset1, writeOffset2, bitsUsed1, bitsUsed2;
tree.GenerateFromFrequencyTable(frequencyTable);
output->WriteCompressed(sizeInBytes);
for (i=0; i < 256; i++)
output->WriteCompressed(frequencyTable[i]);
output->AlignWriteToByteBoundary();
writeOffset1=output->GetWriteOffset();
output->Write((unsigned int)0); // Dummy value
bitsUsed1=output->GetNumberOfBitsUsed();
tree.EncodeArray(userData, sizeInBytes, output);
bitsUsed2=output->GetNumberOfBitsUsed();
writeOffset2=output->GetWriteOffset();
output->SetWriteOffset(writeOffset1);
output->Write(bitsUsed2-bitsUsed1); // Go back and write how many bits were used for the encoding
output->SetWriteOffset(writeOffset2);
}
unsigned DataCompressor::DecompressAndAllocate( RakNet::BitStream * input, unsigned char **output )
{
HuffmanEncodingTree tree;
unsigned int bitsUsed, destinationSizeInBytes;
unsigned int decompressedBytes;
unsigned int frequencyTable[ 256 ];
unsigned i;
input->ReadCompressed(destinationSizeInBytes);
for (i=0; i < 256; i++)
input->ReadCompressed(frequencyTable[i]);
input->AlignReadToByteBoundary();
if (input->Read(bitsUsed)==false)
{
// Read error
#ifdef _DEBUG
RakAssert(0);
#endif
return 0;
}
*output = (unsigned char*) rakMalloc_Ex(destinationSizeInBytes, _FILE_AND_LINE_);
tree.GenerateFromFrequencyTable(frequencyTable);
decompressedBytes=tree.DecodeArray(input, bitsUsed, destinationSizeInBytes, *output );
RakAssert(decompressedBytes==destinationSizeInBytes);
return destinationSizeInBytes;
}

View File

@@ -0,0 +1,33 @@
/// \file DataCompressor.h
/// \brief DataCompressor does compression on a block of data.
/// \details Not very good compression, but it's small and fast so is something you can use per-message at runtime.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __DATA_COMPRESSOR_H
#define __DATA_COMPRESSOR_H
#include "RakMemoryOverride.h"
#include "DS_HuffmanEncodingTree.h"
#include "Export.h"
namespace RakNet
{
/// \brief Does compression on a block of data. Not very good compression, but it's small and fast so is something you can compute at runtime.
class RAK_DLL_EXPORT DataCompressor
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(DataCompressor)
static void Compress( unsigned char *userData, unsigned sizeInBytes, RakNet::BitStream * output );
static unsigned DecompressAndAllocate( RakNet::BitStream * input, unsigned char **output );
};
} // namespace RakNet
#endif

View File

@@ -0,0 +1,242 @@
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_DirectoryDeltaTransfer==1 && _RAKNET_SUPPORT_FileOperations==1
#include "DirectoryDeltaTransfer.h"
#include "FileList.h"
#include "StringCompressor.h"
#include "RakPeerInterface.h"
#include "FileListTransfer.h"
#include "FileListTransferCBInterface.h"
#include "BitStream.h"
#include "MessageIdentifiers.h"
#include "FileOperations.h"
#include "IncrementalReadInterface.h"
using namespace RakNet;
#ifdef _MSC_VER
#pragma warning( push )
#endif
class DDTCallback : public FileListTransferCBInterface
{
public:
unsigned subdirLen;
char outputSubdir[512];
FileListTransferCBInterface *onFileCallback;
DDTCallback() {}
virtual ~DDTCallback() {}
virtual bool OnFile(OnFileStruct *onFileStruct)
{
char fullPathToDir[1024];
if (onFileStruct->fileName && onFileStruct->fileData && subdirLen < strlen(onFileStruct->fileName))
{
strcpy(fullPathToDir, outputSubdir);
strcat(fullPathToDir, onFileStruct->fileName+subdirLen);
WriteFileWithDirectories(fullPathToDir, (char*)onFileStruct->fileData, (unsigned int ) onFileStruct->byteLengthOfThisFile);
}
else
fullPathToDir[0]=0;
return onFileCallback->OnFile(onFileStruct);
}
virtual void OnFileProgress(FileProgressStruct *fps)
{
char fullPathToDir[1024];
if (fps->onFileStruct->fileName && subdirLen < strlen(fps->onFileStruct->fileName))
{
strcpy(fullPathToDir, outputSubdir);
strcat(fullPathToDir, fps->onFileStruct->fileName+subdirLen);
}
else
fullPathToDir[0]=0;
onFileCallback->OnFileProgress(fps);
}
virtual bool OnDownloadComplete(DownloadCompleteStruct *dcs)
{
return onFileCallback->OnDownloadComplete(dcs);
}
};
STATIC_FACTORY_DEFINITIONS(DirectoryDeltaTransfer,DirectoryDeltaTransfer);
DirectoryDeltaTransfer::DirectoryDeltaTransfer()
{
applicationDirectory[0]=0;
fileListTransfer=0;
availableUploads = RakNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
priority=HIGH_PRIORITY;
orderingChannel=0;
incrementalReadInterface=0;
}
DirectoryDeltaTransfer::~DirectoryDeltaTransfer()
{
RakNet::OP_DELETE(availableUploads, _FILE_AND_LINE_);
}
void DirectoryDeltaTransfer::SetFileListTransferPlugin(FileListTransfer *flt)
{
if (fileListTransfer)
{
DataStructures::List<FileListProgress*> fileListProgressList;
fileListTransfer->GetCallbacks(fileListProgressList);
unsigned int i;
for (i=0; i < fileListProgressList.Size(); i++)
availableUploads->RemoveCallback(fileListProgressList[i]);
}
fileListTransfer=flt;
if (flt)
{
DataStructures::List<FileListProgress*> fileListProgressList;
flt->GetCallbacks(fileListProgressList);
unsigned int i;
for (i=0; i < fileListProgressList.Size(); i++)
availableUploads->AddCallback(fileListProgressList[i]);
}
else
{
availableUploads->ClearCallbacks();
}
}
void DirectoryDeltaTransfer::SetApplicationDirectory(const char *pathToApplication)
{
if (pathToApplication==0 || pathToApplication[0]==0)
applicationDirectory[0]=0;
else
{
strncpy(applicationDirectory, pathToApplication, 510);
if (applicationDirectory[strlen(applicationDirectory)-1]!='/' && applicationDirectory[strlen(applicationDirectory)-1]!='\\')
strcat(applicationDirectory, "/");
applicationDirectory[511]=0;
}
}
void DirectoryDeltaTransfer::SetUploadSendParameters(PacketPriority _priority, char _orderingChannel)
{
priority=_priority;
orderingChannel=_orderingChannel;
}
void DirectoryDeltaTransfer::AddUploadsFromSubdirectory(const char *subdir)
{
availableUploads->AddFilesFromDirectory(applicationDirectory, subdir, true, false, true, FileListNodeContext(0,0));
}
unsigned short DirectoryDeltaTransfer::DownloadFromSubdirectory(FileList &localFiles, const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, SystemAddress host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel, FileListProgress *cb)
{
RakAssert(host!=UNASSIGNED_SYSTEM_ADDRESS);
DDTCallback *transferCallback;
localFiles.AddCallback(cb);
// Prepare the callback data
transferCallback = RakNet::OP_NEW<DDTCallback>( _FILE_AND_LINE_ );
if (subdir && subdir[0])
{
transferCallback->subdirLen=(unsigned int)strlen(subdir);
if (subdir[transferCallback->subdirLen-1]!='/' && subdir[transferCallback->subdirLen-1]!='\\')
transferCallback->subdirLen++;
}
else
transferCallback->subdirLen=0;
if (prependAppDirToOutputSubdir)
strcpy(transferCallback->outputSubdir, applicationDirectory);
else
transferCallback->outputSubdir[0]=0;
if (outputSubdir)
strcat(transferCallback->outputSubdir, outputSubdir);
if (transferCallback->outputSubdir[strlen(transferCallback->outputSubdir)-1]!='/' && transferCallback->outputSubdir[strlen(transferCallback->outputSubdir)-1]!='\\')
strcat(transferCallback->outputSubdir, "/");
transferCallback->onFileCallback=onFileCallback;
// Setup the transfer plugin to get the response to this download request
unsigned short setId = fileListTransfer->SetupReceive(transferCallback, true, host);
// Send to the host, telling it to process this request
RakNet::BitStream outBitstream;
outBitstream.Write((MessageID)ID_DDT_DOWNLOAD_REQUEST);
outBitstream.Write(setId);
StringCompressor::Instance()->EncodeString(subdir, 256, &outBitstream);
StringCompressor::Instance()->EncodeString(outputSubdir, 256, &outBitstream);
localFiles.Serialize(&outBitstream);
SendUnified(&outBitstream, _priority, RELIABLE_ORDERED, _orderingChannel, host, false);
return setId;
}
unsigned short DirectoryDeltaTransfer::DownloadFromSubdirectory(const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, SystemAddress host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel, FileListProgress *cb)
{
FileList localFiles;
// Get a hash of all the files that we already have (if any)
localFiles.AddFilesFromDirectory(prependAppDirToOutputSubdir ? applicationDirectory : 0, outputSubdir, true, false, true, FileListNodeContext(0,0));
return DownloadFromSubdirectory(localFiles, subdir, outputSubdir, prependAppDirToOutputSubdir, host, onFileCallback, _priority, _orderingChannel, cb);
}
void DirectoryDeltaTransfer::GenerateHashes(FileList &localFiles, const char *outputSubdir, bool prependAppDirToOutputSubdir)
{
localFiles.AddFilesFromDirectory(prependAppDirToOutputSubdir ? applicationDirectory : 0, outputSubdir, true, false, true, FileListNodeContext(0,0));
}
void DirectoryDeltaTransfer::ClearUploads(void)
{
availableUploads->Clear();
}
void DirectoryDeltaTransfer::OnDownloadRequest(Packet *packet)
{
char subdir[256];
char remoteSubdir[256];
RakNet::BitStream inBitstream(packet->data, packet->length, false);
FileList remoteFileHash;
FileList delta;
unsigned short setId;
inBitstream.IgnoreBits(8);
inBitstream.Read(setId);
StringCompressor::Instance()->DecodeString(subdir, 256, &inBitstream);
StringCompressor::Instance()->DecodeString(remoteSubdir, 256, &inBitstream);
if (remoteFileHash.Deserialize(&inBitstream)==false)
{
#ifdef _DEBUG
RakAssert(0);
#endif
return;
}
availableUploads->GetDeltaToCurrent(&remoteFileHash, &delta, subdir, remoteSubdir);
if (incrementalReadInterface==0)
delta.PopulateDataFromDisk(applicationDirectory, true, false, true);
else
delta.FlagFilesAsReferences();
// This will call the ddtCallback interface that was passed to FileListTransfer::SetupReceive on the remote system
fileListTransfer->Send(&delta, rakPeerInterface, packet->systemAddress, setId, priority, orderingChannel, incrementalReadInterface, chunkSize);
}
PluginReceiveResult DirectoryDeltaTransfer::OnReceive(Packet *packet)
{
switch (packet->data[0])
{
case ID_DDT_DOWNLOAD_REQUEST:
OnDownloadRequest(packet);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
return RR_CONTINUE_PROCESSING;
}
unsigned DirectoryDeltaTransfer::GetNumberOfFilesForUpload(void) const
{
return availableUploads->fileList.Size();
}
void DirectoryDeltaTransfer::SetDownloadRequestIncrementalReadInterface(IncrementalReadInterface *_incrementalReadInterface, unsigned int _chunkSize)
{
incrementalReadInterface=_incrementalReadInterface;
chunkSize=_chunkSize;
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,164 @@
/// \file DirectoryDeltaTransfer.h
/// \brief Simple class to send changes between directories.
/// \details In essence, a simple autopatcher that can be used for transmitting levels, skins, etc.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_DirectoryDeltaTransfer==1 && _RAKNET_SUPPORT_FileOperations==1
#ifndef __DIRECTORY_DELTA_TRANSFER_H
#define __DIRECTORY_DELTA_TRANSFER_H
#include "RakMemoryOverride.h"
#include "RakNetTypes.h"
#include "Export.h"
#include "PluginInterface2.h"
#include "DS_Map.h"
#include "PacketPriority.h"
/// \defgroup DIRECTORY_DELTA_TRANSFER_GROUP DirectoryDeltaTransfer
/// \brief Simple class to send changes between directories
/// \details
/// \ingroup PLUGINS_GROUP
/// \brief Simple class to send changes between directories. In essence, a simple autopatcher that can be used for transmitting levels, skins, etc.
/// \details
/// \sa AutopatcherClient class for database driven patching, including binary deltas and search by date.
///
/// To use, first set the path to your application. For example "C:/Games/MyRPG/"<BR>
/// To allow other systems to download files, call AddUploadsFromSubdirectory, where the parameter is a path relative<BR>
/// to the path to your application. This includes subdirectories.<BR>
/// For example:<BR>
/// SetApplicationDirectory("C:/Games/MyRPG/");<BR>
/// AddUploadsFromSubdirectory("Mods/Skins/");<BR>
/// would allow downloads from<BR>
/// "C:/Games/MyRPG/Mods/Skins/*.*" as well as "C:/Games/MyRPG/Mods/Skins/Level1/*.*"<BR>
/// It would NOT allow downloads from C:/Games/MyRPG/Levels, nor would it allow downloads from C:/Windows<BR>
/// While pathToApplication can be anything you want, applicationSubdirectory must match either partially or fully between systems.
/// \ingroup DIRECTORY_DELTA_TRANSFER_GROUP
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
class FileList;
struct Packet;
struct InternalPacket;
struct DownloadRequest;
class FileListTransfer;
class FileListTransferCBInterface;
class FileListProgress;
class IncrementalReadInterface;
class RAK_DLL_EXPORT DirectoryDeltaTransfer : public PluginInterface2
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(DirectoryDeltaTransfer)
// Constructor
DirectoryDeltaTransfer();
// Destructor
virtual ~DirectoryDeltaTransfer();
/// \brief This plugin has a dependency on the FileListTransfer plugin, which it uses to actually send the files.
/// \details So you need an instance of that plugin registered with RakPeerInterface, and a pointer to that interface should be passed here.
/// \param[in] flt A pointer to a registered instance of FileListTransfer
void SetFileListTransferPlugin(FileListTransfer *flt);
/// \brief Set the local root directory to base all file uploads and downloads off of.
/// \param[in] pathToApplication This path will be prepended to \a applicationSubdirectory in AddUploadsFromSubdirectory to find the actual path on disk.
void SetApplicationDirectory(const char *pathToApplication);
/// \brief What parameters to use for the RakPeerInterface::Send() call when uploading files.
/// \param[in] _priority See RakPeerInterface::Send()
/// \param[in] _orderingChannel See RakPeerInterface::Send()
void SetUploadSendParameters(PacketPriority _priority, char _orderingChannel);
/// \brief Add all files in the specified subdirectory recursively.
/// \details \a subdir is appended to \a pathToApplication in SetApplicationDirectory().
/// All files in the resultant directory and subdirectories are then hashed so that users can download them.
/// \pre You must call SetFileListTransferPlugin with a valid FileListTransfer plugin
/// \param[in] subdir Concatenated with pathToApplication to form the final path from which to allow uploads.
void AddUploadsFromSubdirectory(const char *subdir);
/// \brief Downloads files from the matching parameter \a subdir in AddUploadsFromSubdirectory.
/// \details \a subdir must contain all starting characters in \a subdir in AddUploadsFromSubdirectory
/// Therefore,
/// AddUploadsFromSubdirectory("Levels/Level1/"); would allow you to download using DownloadFromSubdirectory("Levels/Level1/Textures/"...
/// but it would NOT allow you to download from DownloadFromSubdirectory("Levels/"... or DownloadFromSubdirectory("Levels/Level2/"...
/// \pre You must call SetFileListTransferPlugin with a valid FileListTransfer plugin
/// \note Blocking. Will block while hashes of the local files are generated
/// \param[in] subdir A directory passed to AddUploadsFromSubdirectory on the remote system. The passed dir can be more specific than the remote dir.
/// \param[in] outputSubdir The directory to write the output to. Usually this will match \a subdir but it can be different if you want.
/// \param[in] prependAppDirToOutputSubdir True to prepend outputSubdir with pathToApplication when determining the final output path. Usually you want this to be true.
/// \param[in] host The address of the remote system to send the message to.
/// \param[in] onFileCallback Callback to call per-file (optional). When fileIndex+1==setCount in the callback then the download is done
/// \param[in] _priority See RakPeerInterface::Send()
/// \param[in] _orderingChannel See RakPeerInterface::Send()
/// \param[in] cb Callback to get progress updates. Pass 0 to not use.
/// \return A set ID, identifying this download set. Returns 65535 on host unreachable.
unsigned short DownloadFromSubdirectory(const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, SystemAddress host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel, FileListProgress *cb);
/// \brief Downloads files from the matching parameter \a subdir in AddUploadsFromSubdirectory.
/// \details \a subdir must contain all starting characters in \a subdir in AddUploadsFromSubdirectory
/// Therefore,
/// AddUploadsFromSubdirectory("Levels/Level1/"); would allow you to download using DownloadFromSubdirectory("Levels/Level1/Textures/"...
/// but it would NOT allow you to download from DownloadFromSubdirectory("Levels/"... or DownloadFromSubdirectory("Levels/Level2/"...
/// \pre You must call SetFileListTransferPlugin with a valid FileListTransfer plugin
/// \note Nonblocking, but requires call to GenerateHashes()
/// \param[in] localFiles Hashes of local files already on the harddrive. Populate with GenerateHashes(), which you may wish to call from a thread
/// \param[in] subdir A directory passed to AddUploadsFromSubdirectory on the remote system. The passed dir can be more specific than the remote dir.
/// \param[in] outputSubdir The directory to write the output to. Usually this will match \a subdir but it can be different if you want.
/// \param[in] prependAppDirToOutputSubdir True to prepend outputSubdir with pathToApplication when determining the final output path. Usually you want this to be true.
/// \param[in] host The address of the remote system to send the message to.
/// \param[in] onFileCallback Callback to call per-file (optional). When fileIndex+1==setCount in the callback then the download is done
/// \param[in] _priority See RakPeerInterface::Send()
/// \param[in] _orderingChannel See RakPeerInterface::Send()
/// \param[in] cb Callback to get progress updates. Pass 0 to not use.
/// \return A set ID, identifying this download set. Returns 65535 on host unreachable.
unsigned short DownloadFromSubdirectory(FileList &localFiles, const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, SystemAddress host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel, FileListProgress *cb);
/// Hash files already on the harddrive, in preparation for a call to DownloadFromSubdirectory(). Passed to second version of DownloadFromSubdirectory()
/// This is slow, and it is exposed so you can call it from a thread before calling DownloadFromSubdirectory()
/// \param[out] localFiles List of hashed files populated from \a outputSubdir and \a prependAppDirToOutputSubdir
/// \param[in] outputSubdir The directory to write the output to. Usually this will match \a subdir but it can be different if you want.
/// \param[in] prependAppDirToOutputSubdir True to prepend outputSubdir with pathToApplication when determining the final output path. Usually you want this to be true.
void GenerateHashes(FileList &localFiles, const char *outputSubdir, bool prependAppDirToOutputSubdir);
/// \brief Clear all allowed uploads previously set with AddUploadsFromSubdirectory
void ClearUploads(void);
/// \brief Returns how many files are available for upload
/// \return How many files are available for upload
unsigned GetNumberOfFilesForUpload(void) const;
/// \brief Normally, if a remote system requests files, those files are all loaded into memory and sent immediately.
/// \details This function allows the files to be read in incremental chunks, saving memory
/// \param[in] _incrementalReadInterface If a file in \a fileList has no data, filePullInterface will be used to read the file in chunks of size \a chunkSize
/// \param[in] _chunkSize How large of a block of a file to send at once
void SetDownloadRequestIncrementalReadInterface(IncrementalReadInterface *_incrementalReadInterface, unsigned int _chunkSize);
/// \internal For plugin handling
virtual PluginReceiveResult OnReceive(Packet *packet);
protected:
void OnDownloadRequest(Packet *packet);
char applicationDirectory[512];
FileListTransfer *fileListTransfer;
FileList *availableUploads;
PacketPriority priority;
char orderingChannel;
IncrementalReadInterface *incrementalReadInterface;
unsigned int chunkSize;
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,236 @@
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_DynDNS==1 && _RAKNET_SUPPORT_TCPInterface==1
#include "TCPInterface.h"
#include "SocketLayer.h"
#include "DynDNS.h"
#include "GetTime.h"
using namespace RakNet;
struct DynDnsResult
{
const char *description;
const char *code;
DynDnsResultCode resultCode;
};
DynDnsResult resultTable[13] =
{
// See http://www.dyndns.com/developers/specs/flow.pdf
{"DNS update success.\nPlease wait up to 60 seconds for the change to take effect.\n", "good", RC_SUCCESS}, // Even with success, it takes time for the cache to update!
{"No change", "nochg", RC_NO_CHANGE},
{"Host has been blocked. You will need to contact DynDNS to reenable.", "abuse", RC_ABUSE},
{"Useragent is blocked", "badagent", RC_BAD_AGENT},
{"Username/password pair bad", "badauth", RC_BAD_AUTH},
{"Bad system parameter", "badsys", RC_BAD_SYS},
{"DNS inconsistency", "dnserr", RC_DNS_ERROR},
{"Paid account feature", "!donator", RC_NOT_DONATOR},
{"No such host in system", "nohost", RC_NO_HOST},
{"Invalid hostname format", "notfqdn", RC_NOT_FQDN},
{"Serious error", "numhost", RC_NUM_HOST},
{"This host exists, but does not belong to you", "!yours", RC_NOT_YOURS},
{"911", "911", RC_911},
};
DynDNS::DynDNS()
{
connectPhase=CP_IDLE;
tcp=0;
}
DynDNS::~DynDNS()
{
if (tcp)
RakNet::OP_DELETE(tcp, _FILE_AND_LINE_);
}
void DynDNS::Stop(void)
{
tcp->Stop();
connectPhase = CP_IDLE;
RakNet::OP_DELETE(tcp, _FILE_AND_LINE_);
tcp=0;
}
// newIPAddress is optional - if left out, DynDNS will use whatever it receives
void DynDNS::UpdateHostIP(const char *dnsHost, const char *newIPAddress, const char *usernameAndPassword )
{
myIPStr[0]=0;
if (tcp==0)
tcp = RakNet::OP_NEW<TCPInterface>(_FILE_AND_LINE_);
connectPhase = CP_IDLE;
host = dnsHost;
if (tcp->Start(0, 1)==false)
{
SetCompleted(RC_TCP_FAILED_TO_START, "TCP failed to start");
return;
}
connectPhase = CP_CONNECTING_TO_CHECKIP;
tcp->Connect("checkip.dyndns.org", 80, false);
// See https://www.dyndns.com/developers/specs/syntax.html
getString="GET /nic/update?hostname=";
getString+=dnsHost;
if (newIPAddress)
{
getString+="&myip=";
getString+=newIPAddress;
}
getString+="&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG HTTP/1.0\n";
getString+="Host: members.dyndns.org\n";
getString+="Authorization: Basic ";
char outputData[512];
TCPInterface::Base64Encoding(usernameAndPassword, (int) strlen(usernameAndPassword), outputData);
getString+=outputData;
getString+="User-Agent: Jenkins Software LLC - PC - 1.0\n\n";
}
void DynDNS::Update(void)
{
if (connectPhase==CP_IDLE)
return;
serverAddress=tcp->HasFailedConnectionAttempt();
if (serverAddress!=UNASSIGNED_SYSTEM_ADDRESS)
{
SetCompleted(RC_TCP_DID_NOT_CONNECT, "Could not connect to DynDNS");
return;
}
serverAddress=tcp->HasCompletedConnectionAttempt();
if (serverAddress!=UNASSIGNED_SYSTEM_ADDRESS)
{
if (connectPhase == CP_CONNECTING_TO_CHECKIP)
{
checkIpAddress=serverAddress;
connectPhase = CP_WAITING_FOR_CHECKIP_RESPONSE;
tcp->Send("GET\n\n", (unsigned int) strlen("GET\n\n"), serverAddress, false); // Needs 2 newlines! This is not documented and wasted a lot of my time
}
else
{
connectPhase = CP_WAITING_FOR_DYNDNS_RESPONSE;
tcp->Send(getString.C_String(), (unsigned int) getString.GetLength(), serverAddress, false);
}
phaseTimeout=RakNet::GetTime()+1000;
}
if (connectPhase==CP_WAITING_FOR_CHECKIP_RESPONSE && RakNet::GetTime()>phaseTimeout)
{
connectPhase = CP_CONNECTING_TO_DYNDNS;
tcp->CloseConnection(checkIpAddress);
tcp->Connect("members.dyndns.org", 80, false);
}
else if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE && RakNet::GetTime()>phaseTimeout)
{
SetCompleted(RC_DYNDNS_TIMEOUT, "DynDNS did not respond");
return;
}
Packet *packet = tcp->Receive();
if (packet)
{
if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE)
{
unsigned int i;
char *result;
result=strstr((char*) packet->data, "Connection: close");
if (result!=0)
{
result+=strlen("Connection: close");
while (*result && (*result=='\r') || (*result=='\n') || (*result==' ') )
result++;
for (i=0; i < 13; i++)
{
if (strncmp(resultTable[i].code, result, strlen(resultTable[i].code))==0)
{
if (resultTable[i].resultCode==RC_SUCCESS)
{
// Read my external IP into myIPStr
// Advance until we hit a number
while (*result && ((*result<'0') || (*result>'9')) )
result++;
if (*result)
{
SystemAddress parser;
parser.FromString(result);
parser.ToString(false, myIPStr);
}
}
tcp->DeallocatePacket(packet);
SetCompleted(resultTable[i].resultCode, resultTable[i].description);
break;
}
}
if (i==13)
{
tcp->DeallocatePacket(packet);
SetCompleted(RC_UNKNOWN_RESULT, "DynDNS returned unknown result");
}
}
else
{
tcp->DeallocatePacket(packet);
SetCompleted(RC_PARSING_FAILURE, "Parsing failure on returned string from DynDNS");
}
return;
}
else
{
/*
HTTP/1.1 200 OK
Content-Type: text/html
Server: DynDNS-CheckIP/1.0
Connection: close
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 105
<html><head><title>Current IP Check</title></head><body>Current IP Address: 98.1
89.219.22</body></html>
Connection to host lost.
*/
char *result;
result=strstr((char*) packet->data, "Current IP Address: ");
if (result!=0)
{
result+=strlen("Current IP Address: ");
SystemAddress myIp;
myIp.FromString(result);
myIp.ToString(false, myIPStr);
// Resolve DNS we are setting. If equal to current then abort
const char *existingHost = ( char* ) SocketLayer::DomainNameToIP( host.C_String() );
if (existingHost && strcmp(existingHost, myIPStr)==0)
{
// DynDNS considers setting the IP to what it is already set abuse
tcp->DeallocatePacket(packet);
SetCompleted(RC_DNS_ALREADY_SET, "No action needed");
return;
}
}
tcp->DeallocatePacket(packet);
tcp->CloseConnection(packet->systemAddress);
connectPhase = CP_CONNECTING_TO_DYNDNS;
tcp->Connect("members.dyndns.org", 80, false);
}
}
if (tcp->HasLostConnection()!=UNASSIGNED_SYSTEM_ADDRESS)
{
if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE)
{
SetCompleted(RC_CONNECTION_LOST_WITHOUT_RESPONSE, "Connection lost to DynDNS during GET operation");
}
}
}
#endif // _RAKNET_SUPPORT_DynDNS

View File

@@ -0,0 +1,100 @@
/// \file DynDNS.h
/// \brief Helper to class to update DynDNS
/// This can be used to determine what permissions are should be allowed to the other system
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_DynDNS==1 && _RAKNET_SUPPORT_TCPInterface==1
class TCPInterface;
#ifndef __DYN_DNS_H
#define __DYN_DNS_H
namespace RakNet
{
enum DynDnsResultCode
{
// ----- Success -----
RC_SUCCESS,
RC_DNS_ALREADY_SET, // RakNet detects no action is needed
// ----- Ignorable failure (treat same as success) -----
RC_NO_CHANGE, // DynDNS detects no action is needed (treated as abuse though)
// ----- User error -----
RC_NOT_DONATOR, // You have to pay to do this
RC_NO_HOST, // This host does not exist at all
RC_BAD_AUTH, // You set the wrong password
RC_NOT_YOURS, // This is not your host
// ----- Permanent failure -----
RC_ABUSE, // Your host has been blocked, too many failures disable your account
RC_TCP_FAILED_TO_START, // TCP port already in use
RC_TCP_DID_NOT_CONNECT, // DynDNS down?
RC_UNKNOWN_RESULT, // DynDNS returned a result code that was not documented as of 12/4/2010 on http://www.dyndns.com/developers/specs/flow.pdf
RC_PARSING_FAILURE, // Can't read the result returned, format change?
RC_CONNECTION_LOST_WITHOUT_RESPONSE, // Lost the connection to DynDNS while communicating
RC_BAD_AGENT, // ???
RC_BAD_SYS, // ???
RC_DNS_ERROR, // ???
RC_NOT_FQDN, // ???
RC_NUM_HOST, // ???
RC_911, // ???
RC_DYNDNS_TIMEOUT, // DynDNS did not respond
};
// Can only process one at a time with the current implementation
class RAK_DLL_EXPORT DynDNS
{
public:
DynDNS();
~DynDNS();
// Pass 0 for newIPAddress to autodetect whatever you are uploading from
// usernameAndPassword should be in the format username:password
void UpdateHostIP(const char *dnsHost, const char *newIPAddress, const char *usernameAndPassword );
void Update(void);
// Output
bool IsRunning(void) const {return connectPhase!=CP_IDLE;}
bool IsCompleted(void) const {return connectPhase==CP_IDLE;}
RakNet::DynDnsResultCode GetCompletedResultCode(void) {return result;}
const char *GetCompletedDescription(void) const {return resultDescription;}
bool WasResultSuccessful(void) const {return result==RC_SUCCESS || result==RC_DNS_ALREADY_SET || result==RC_NO_CHANGE;}
char *GetMyPublicIP(void) const {return (char*) myIPStr;} // We get our public IP as part of the process. This is valid once completed
protected:
void Stop(void);
void SetCompleted(RakNet::DynDnsResultCode _result, const char *_resultDescription) {Stop(); result=_result; resultDescription=_resultDescription;}
enum ConnectPhase
{
CP_CONNECTING_TO_CHECKIP,
CP_WAITING_FOR_CHECKIP_RESPONSE,
CP_CONNECTING_TO_DYNDNS,
CP_WAITING_FOR_DYNDNS_RESPONSE,
CP_IDLE,
};
TCPInterface *tcp;
RakNet::RakString getString;
SystemAddress serverAddress;
ConnectPhase connectPhase;
RakNet::RakString host;
RakNet::Time phaseTimeout;
SystemAddress checkIpAddress;
const char *resultDescription;
RakNet::DynDnsResultCode result;
char myIPStr[32];
};
} // namespace RakNet
#endif // __DYN_DNS_H
#endif // _RAKNET_SUPPORT_DynDNS

View File

@@ -0,0 +1,362 @@
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_EmailSender==1 && _RAKNET_SUPPORT_TCPInterface==1 && _RAKNET_SUPPORT_FileOperations==1
// Useful sites
// http://www.faqs.org\rfcs\rfc2821.html
// http://www2.rad.com\networks/1995/mime/examples.htm
#include "EmailSender.h"
#include "TCPInterface.h"
#include "GetTime.h"
#include "Rand.h"
#include "FileList.h"
#include "BitStream.h"
#include <stdio.h>
#include "RakSleep.h"
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(EmailSender,EmailSender);
const char *EmailSender::Send(const char *hostAddress, unsigned short hostPort, const char *sender, const char *recipient, const char *senderName, const char *recipientName, const char *subject, const char *body, FileList *attachedFiles, bool doPrintf, const char *password)
{
RakNet::Packet *packet;
char query[1024];
TCPInterface tcpInterface;
SystemAddress emailServer;
if (tcpInterface.Start(0, 0)==false)
return "Unknown error starting TCP";
emailServer=tcpInterface.Connect(hostAddress, hostPort,true);
if (emailServer==UNASSIGNED_SYSTEM_ADDRESS)
return "Failed to connect to host";
#if OPEN_SSL_CLIENT_SUPPORT==1
tcpInterface.StartSSLClient(emailServer);
#endif
RakNet::TimeMS timeoutTime = RakNet::GetTimeMS()+3000;
packet=0;
while (RakNet::GetTimeMS() < timeoutTime)
{
packet = tcpInterface.Receive();
if (packet)
{
if (doPrintf)
RAKNET_DEBUG_PRINTF("%s", packet->data);
break;
}
RakSleep(250);
}
if (packet==0)
return "Timeout while waiting for initial data from server.";
tcpInterface.Send("EHLO\r\n", 6, emailServer,false);
const char *response;
bool authenticate=false;
#ifdef _MSC_VER
#pragma warning(disable:4127) // conditional expression is constant
#endif
while (1)
{
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0 && strcmp(response, "AUTHENTICATE")==0)
{
authenticate=true;
break;
}
// Something other than continue?
if (response!=0 && strcmp(response, "CONTINUE")!=0)
return response;
// Success?
if (response==0)
break;
}
if (authenticate)
{
sprintf(query, "EHLO %s\r\n", sender);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
if (password==0)
return "Password needed";
char *outputData = RakNet::OP_NEW_ARRAY<char >((const int) (strlen(sender)+strlen(password)+2)*3, _FILE_AND_LINE_ );
RakNet::BitStream bs;
char zero=0;
bs.Write(&zero,1);
bs.Write(sender,(const unsigned int)strlen(sender));
//bs.Write("jms1@jms1.net",(const unsigned int)strlen("jms1@jms1.net"));
bs.Write(&zero,1);
bs.Write(password,(const unsigned int)strlen(password));
bs.Write(&zero,1);
//bs.Write("not.my.real.password",(const unsigned int)strlen("not.my.real.password"));
TCPInterface::Base64Encoding((const char*)bs.GetData(), bs.GetNumberOfBytesUsed(), outputData);
sprintf(query, "AUTH PLAIN %s", outputData);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
}
if (sender)
sprintf(query, "MAIL From: <%s>\r\n", sender);
else
sprintf(query, "MAIL From: <>\r\n");
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
if (recipient)
sprintf(query, "RCPT TO: <%s>\r\n", recipient);
else
sprintf(query, "RCPT TO: <>\r\n");
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
tcpInterface.Send("DATA\r\n", (unsigned int)strlen("DATA\r\n"), emailServer,false);
// Wait for 354...
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
if (subject)
{
sprintf(query, "Subject: %s\r\n", subject);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
}
if (senderName)
{
sprintf(query, "From: %s\r\n", senderName);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
}
if (recipientName)
{
sprintf(query, "To: %s\r\n", recipientName);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
}
const int boundarySize=60;
char boundary[boundarySize+1];
int i,j;
if (attachedFiles && attachedFiles->fileList.Size())
{
rakNetRandom.SeedMT((unsigned int) RakNet::GetTimeMS());
// Random multipart message boundary
for (i=0; i < boundarySize; i++)
boundary[i]=TCPInterface::Base64Map()[rakNetRandom.RandomMT()%64];
boundary[boundarySize]=0;
}
sprintf(query, "MIME-version: 1.0\r\n");
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
if (attachedFiles && attachedFiles->fileList.Size())
{
sprintf(query, "Content-type: multipart/mixed; BOUNDARY=\"%s\"\r\n\r\n", boundary);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
sprintf(query, "This is a multi-part message in MIME format.\r\n\r\n--%s\r\n", boundary);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
}
sprintf(query, "Content-Type: text/plain; charset=\"US-ASCII\"\r\n\r\n");
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
// Write the body of the email, doing some lame shitty shit where I have to make periods at the start of a newline have a second period.
char *newBody;
int bodyLength;
bodyLength=(int)strlen(body);
newBody = (char*) rakMalloc_Ex( bodyLength*3, _FILE_AND_LINE_ );
if (bodyLength>0)
newBody[0]=body[0];
for (i=1, j=1; i < bodyLength; i++)
{
// Transform \n . \r \n into \n . . \r \n
if (i < bodyLength-2 &&
body[i-1]=='\n' &&
body[i+0]=='.' &&
body[i+1]=='\r' &&
body[i+2]=='\n')
{
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='\r';
newBody[j++]='\n';
i+=2;
}
// Transform \n . . \r \n into \n . . . \r \n
// Having to process .. is a bug in the mail server - the spec says ONLY \r\n.\r\n should be transformed
else if (i <= bodyLength-3 &&
body[i-1]=='\n' &&
body[i+0]=='.' &&
body[i+1]=='.' &&
body[i+2]=='\r' &&
body[i+3]=='\n')
{
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='\r';
newBody[j++]='\n';
i+=3;
}
// Transform \n . \n into \n . . \r \n (this is a bug in the mail server - the spec says do not count \n alone but it does)
else if (i < bodyLength-1 &&
body[i-1]=='\n' &&
body[i+0]=='.' &&
body[i+1]=='\n')
{
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='\r';
newBody[j++]='\n';
i+=1;
}
// Transform \n . . \n into \n . . . \r \n (this is a bug in the mail server - the spec says do not count \n alone but it does)
// In fact having to process .. is a bug too - because the spec says ONLY \r\n.\r\n should be transformed
else if (i <= bodyLength-2 &&
body[i-1]=='\n' &&
body[i+0]=='.' &&
body[i+1]=='.' &&
body[i+2]=='\n')
{
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='\r';
newBody[j++]='\n';
i+=2;
}
else
newBody[j++]=body[i];
}
newBody[j++]='\r';
newBody[j++]='\n';
tcpInterface.Send(newBody, j, emailServer,false);
rakFree_Ex(newBody, _FILE_AND_LINE_ );
int outputOffset;
// What a pain in the rear. I have to map the binary to printable characters using 6 bits per character.
if (attachedFiles && attachedFiles->fileList.Size())
{
for (i=0; i < (int) attachedFiles->fileList.Size(); i++)
{
// Write boundary
sprintf(query, "\r\n--%s\r\n", boundary);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
sprintf(query, "Content-Type: APPLICATION/Octet-Stream; SizeOnDisk=%i; name=\"%s\"\r\nContent-Transfer-Encoding: BASE64\r\nContent-Description: %s\r\n\r\n", attachedFiles->fileList[i].dataLengthBytes, attachedFiles->fileList[i].filename.C_String(), attachedFiles->fileList[i].filename.C_String());
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
newBody = (char*) rakMalloc_Ex( (size_t) (attachedFiles->fileList[i].dataLengthBytes*3)/2, _FILE_AND_LINE_ );
outputOffset=TCPInterface::Base64Encoding(attachedFiles->fileList[i].data, (int) attachedFiles->fileList[i].dataLengthBytes, newBody);
// Send the base64 mapped file.
tcpInterface.Send(newBody, outputOffset, emailServer,false);
rakFree_Ex(newBody, _FILE_AND_LINE_ );
}
// Write last boundary
sprintf(query, "\r\n--%s--\r\n", boundary);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
}
sprintf(query, "\r\n.\r\n");
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
tcpInterface.Send("QUIT\r\n", (unsigned int)strlen("QUIT\r\n"), emailServer,false);
RakSleep(30);
if (doPrintf)
{
packet = tcpInterface.Receive();
while (packet)
{
RAKNET_DEBUG_PRINTF("%s", packet->data);
packet = tcpInterface.Receive();
}
}
tcpInterface.Stop();
return 0; // Success
}
const char *EmailSender::GetResponse(TCPInterface *tcpInterface, const SystemAddress &emailServer, bool doPrintf)
{
RakNet::Packet *packet;
RakNet::TimeMS timeout;
timeout=RakNet::GetTimeMS()+5000;
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while (1)
{
if (tcpInterface->HasLostConnection()==emailServer)
return "Connection to server lost.";
packet = tcpInterface->Receive();
if (packet)
{
if (doPrintf)
{
RAKNET_DEBUG_PRINTF("%s", packet->data);
}
#if OPEN_SSL_CLIENT_SUPPORT==1
if (strstr((const char*)packet->data, "220"))
{
tcpInterface->StartSSLClient(packet->systemAddress);
return "AUTHENTICATE"; // OK
}
// if (strstr((const char*)packet->data, "250-AUTH LOGIN PLAIN"))
// {
// tcpInterface->StartSSLClient(packet->systemAddress);
// return "AUTHENTICATE"; // OK
// }
#endif
if (strstr((const char*)packet->data, "235"))
return 0; // Authentication accepted
if (strstr((const char*)packet->data, "354"))
return 0; // Go ahead
#if OPEN_SSL_CLIENT_SUPPORT==1
if (strstr((const char*)packet->data, "250-STARTTLS"))
{
tcpInterface->Send("STARTTLS\r\n", (unsigned int) strlen("STARTTLS\r\n"), packet->systemAddress, false);
return "CONTINUE";
}
#endif
if (strstr((const char*)packet->data, "250"))
return 0; // OK
if (strstr((const char*)packet->data, "550"))
return "Failed on error code 550";
if (strstr((const char*)packet->data, "553"))
return "Failed on error code 553";
}
if (RakNet::GetTimeMS() > timeout)
return "Timed out";
RakSleep(100);
}
}
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,58 @@
/// \file EmailSender.h
/// \brief Rudimentary class to send email from code. Don't expect anything fancy.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_EmailSender==1 && _RAKNET_SUPPORT_TCPInterface==1 && _RAKNET_SUPPORT_FileOperations==1
#ifndef __EMAIL_SENDER_H
#define __EMAIL_SENDER_H
#include "RakNetTypes.h"
#include "RakMemoryOverride.h"
#include "Export.h"
#include "Rand.h"
#include "TCPInterface.h"
namespace RakNet
{
/// Forward declarations
class FileList;
class TCPInterface;
/// \brief Rudimentary class to send email from code.
class RAK_DLL_EXPORT EmailSender
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(EmailSender)
/// \brief Sends an email.
/// \param[in] hostAddress The address of the email server.
/// \param[in] hostPort The port of the email server (usually 25)
/// \param[in] sender The email address you are sending from.
/// \param[in] recipient The email address you are sending to.
/// \param[in] senderName The email address you claim to be sending from
/// \param[in] recipientName The email address you claim to be sending to
/// \param[in] subject Email subject
/// \param[in] body Email body
/// \param[in] attachedFiles List of files to attach to the email. (Can be 0 to send none).
/// \param[in] doPrintf true to output SMTP info to console(for debugging?)
/// \param[in] password Used if the server uses AUTHENTICATE PLAIN over TLS (such as gmail)
/// \return 0 on success, otherwise a string indicating the error message
const char *Send(const char *hostAddress, unsigned short hostPort, const char *sender, const char *recipient, const char *senderName, const char *recipientName, const char *subject, const char *body, FileList *attachedFiles, bool doPrintf, const char *password);
protected:
const char *GetResponse(TCPInterface *tcpInterface, const SystemAddress &emailServer, bool doPrintf);
RakNetRandom rakNetRandom;
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,35 @@
#include "FormatString.h"
#include "EpochTimeToString.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
// localtime
#include <time.h>
#include "LinuxStrings.h"
char * EpochTimeToString(long long time)
{
static int textIndex=0;
static char text[4][64];
if (++textIndex==4)
textIndex=0;
struct tm * timeinfo;
time_t t = time;
timeinfo = localtime ( &t );
strftime (text[textIndex],64,"%c.",timeinfo);
/*
time_t
// Copied from the docs
struct tm *newtime;
newtime = _localtime64(& time);
asctime_s( text[textIndex], sizeof(text[textIndex]), newtime );
while (text[textIndex][0] && (text[textIndex][strlen(text[textIndex])-1]=='\n' || text[textIndex][strlen(text[textIndex])-1]=='\r'))
text[textIndex][strlen(text[textIndex])-1]=0;
*/
return text[textIndex];
}

View File

@@ -0,0 +1,15 @@
/// \file EpochTimeToString.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __EPOCH_TIME_TO_STRING_H
#define __EPOCH_TIME_TO_STRING_H
#include "Export.h"
RAK_DLL_EXPORT char * EpochTimeToString(long long time);
#endif

View File

@@ -0,0 +1,13 @@
#include "RakNetDefines.h"
#if defined(_WIN32) && !(defined(__GNUC__) || defined(__GCCXML__)) && !defined(_RAKNET_LIB) && defined(_RAKNET_DLL)
#define RAK_DLL_EXPORT __declspec(dllexport)
#else
#define RAK_DLL_EXPORT
#endif
#define STATIC_FACTORY_DECLARATIONS(x) static x* GetInstance(void); \
static void DestroyInstance( x *i);
#define STATIC_FACTORY_DEFINITIONS(x,y) x* x::GetInstance(void) {return RakNet::OP_NEW<y>( _FILE_AND_LINE_ );} \
void x::DestroyInstance( x *i) {RakNet::OP_DELETE(( y* ) i, _FILE_AND_LINE_);}

Some files were not shown because too many files have changed in this diff Show More