Jump to content

  • Log In with Google      Sign In   
  • Create Account

Vincent_M

Member Since 16 Jan 2007
Offline Last Active Mar 03 2016 09:41 PM

Posts I've Made

In Topic: Real-Time WebSockets and REST

07 October 2015 - 06:33 AM

I always applaud the idea of using well known, highly used authentication and authorization methods. The only other response I have at this time is the group will probably be more help if you begin implementing your system and ask specific questions. The architecture side of things will always be debatable and to be honest nothing you are suggesting is standout wrong per say. That being said I would imagine your troubles here won't be with your architecture so much as the network jitter and delay interpolation etc on a space shmup.

I debated about this: start now, and ask questions later, or sit back and contemplate. I chose to ask about architecture first, but ended up diving into the project. I initially wanted to ask about jitter, latency and how to combat that with dead reckoning/path interpolation. I've read some papers online, but wanted to go over the specifics of it once I actually had something implemented. I ran into the issue of writing my Asteroids MMO using TCP sockets a few years back. It was a nightmare attempting to convert it to UDP, and I never finished UDP support. The game was plagued by jittering when 2 players weren't playing off of the same access point.

 

I started writing a REST API for user account management. This allows users to create new accounts, verify them via email, change emails, rename usernames, etc. I'm thinking about using it for login purposes too, but my NodeJS server could handle this as well since it'll have database access as well. At my old job, we would pass an API-KEY key-value pair in each of our REST API's requests. The request would return an error if the call didn't include it with the correct value. I've seen the use of API keys mentioned online, but I don't see the point of using them. Anyway monitoring the API calls via proxy can figure what the key is, so why bother using one? Is this to help stave some type of automated attacks not specifically targeted at my application?

 

REST-ful-ness and real-time do not mix well. REST is almost purposefully not a real-time design approach, as it trades overhead for long-term evolution robustness. Games can almost always update client and server in parallel, and thus don't often benefit from this (but still have to pay the cost.)

I think your approach sounds like it can work. Some caveats:
Web sockets are TCP -- there will be occasional lag. However, if you've played agar.io, or Realm of the Mad God, you would know how good/bad this is.
Use HTTPS or TLS for all requests if possible. While HTTP and TCP is easier for debugging, there are so many weaknesses with plaintext these days that HTTPS and TLS is pretty much a must.
Amazon may suffer the "noisy neighbors" syndrome, where suddenly you suffer a lot of performance degradation, jitter, packet loss, etc. Beware, monitor, and be prepared to move to another availability zone if it gets bad.

Feel free to come back and ask for more specific questions if you need to, or just keep us all up to date on how the project goes!

I thought agar.io used WebSockets. Player movement didn't appear to have any jittering unless players broke into smaller pieces since you'd suddenly have a bunch of objects moving much faster onscreen. I'll ask more specific questions when I get to that point. Working on the sign-in system.

 


I have been using redis a lot for a bunch of work projects as a lightweight message bus (as opposed to the bulky more feature rich and complex enterprise ready RabbitMQ)

I've used both in Python projects at work in the past, but not in PHP yet. I'm considering Redis for sessions, possibly

 


Note that you can WebRTC datachannels to get UDP communication going.

Perfect! I've wondered if there was a UDP counterpart to WebSockets. I've searched online in the past, but found nothing. When I first read about WebRTC, I also wondered how supported it'd be client-side in browsers, but I think it'll work for my purposes. I'm writing stuff that's meant to be more experimental than mass-supported.

 


In regards to WebSockets I personally wouldn't bother with socket.io. You can just use the ws module for games. Falling back isn't necessary anymore for any browser or device. (Nor for a game is it really worth it).

I've wondered about this myself. It seems like WebSockets have been widely supported since 2012.


In Topic: Different Resolutions

23 August 2015 - 05:08 PM


In other games, like the example in the picture, more screen real estate means a big advantage in what you can select or activate. The player with the smallest screen (red) can only see a few units and will be surprised as more come on the screen.

That's a pretty good point. I remember some one mentioning in a thread a while back where he'd play Age of Kings on his 4K display. The largest map sizes could fit entirely onscreen. This could be an advantage or disadvantage to the users depending on how physically large the display is (32-inch vs 60-inch). The was we addressed the varying aspect ratio issue in one of our portrait mobile games was similar to how the Warcraft games did it. We'd use our skinniest aspect ratio (9:16) as our basis aspect ratio to draw all UI elements within. The game would be in full-view, while our widest aspect ratio (3:4) would have decorative borders on the sides. We used a fixed-height Cartesian coordinate system where the origin was at the center of the screen, and the y-axis was facing up rather than down. We'd place all of our UI elements within the bounds of the 9:16 aspect ratio, and add those borders just outside of the 9:16 view. Then, wider aspect ratios would show the border. How much of the border it showed depended on how close to 3:4 we got.

 

I believe we used a fixed height of 720, or maybe we used 960. In any case, this was only for placement and size (including 9-slicing). We were targeting the iPad retina as our highest possible resolution, so our fullscreen backgrounds were 1536x2048 (3:4). In a 720 fixed-height coordinate space, we get a viewport range of +/-360, which covers 2048 pixels down on a retina iPad.

 

This can all be accomplished with the projection matrix, as mentioned by IceCave.


In Topic: Career Advice

20 August 2015 - 04:48 PM

I've been really tired today, so I'm struggling to conveying my thoughts.

 

1. Of course, it'll take more time. I've spent a long time trying to get experience on my own, but it appears that I'm only working harder, not smarter. I can't seem to focus on a single thing.

 

2. I'm picky because I'm so new to the software engineering industry in general. I don't know what most job roles really are, or even what I want. Not only do I need experience, but I need to figure out what I want that's obtainable right now. Then, I actually know what to work toward.

 

3. I meant seniors. Most companies are looking for senior developers, not junior developers.

 

4. I mean not knowledgable in as "uncomfortable." If I see something I might be interested in, should I just go for it? Getting a job in that field could land me a reliable mentor.

 

5. This warrants a quote:


5. Great. You can. You'll just need to get some experience in the industry, and good contacts, a plan, and some money. My article 29 might shed more light on that part. http://www.sloperama.com/advice/lesson29.htm

I completely agree. I need more experience in the industry in general, not just programming. I try to balance my programming, gym and family time with a social life too. I'm always meeting people who want to do projects with me. Again, lack of experience and direction on my part's destroyed every personal project I've done to date. I'll post back when I'm done reading your article.


In Topic: Matching Parameter Names to Class Fields

19 August 2015 - 02:41 PM


It's fine. Just use "this->name = name" so you don't try to assign the parameter to itself:

I use "this->" when I have identical names. When assigning variables in the constructor's initializer list, it seems to not make any different that my constructor parameters have identical names to my

 

 

 


The only problem here is your passing of POD types by const reference.

Do class fields still get assigned by value when passing by reference, right? One thing that confuses me about references is that I can still pass literals in as parameters. I've just recently started using references, so it's practice at this point. If I wanted to pass objects by value into my constructor (aka, make a copy, just like push_back() does in std::vector), would I not use a reference? If I wanted to pass by reference, would it be wise to use references over pointers, or does it depend on the situation?

 

 

 


I like to prefix all class member variables with "m" which happens to avoid this issue and make it clearer which variables are member variables. I believe the "m_" prefix is the most common (based on what ive seen), Im just too lazy to type the underscore. Like how some people start all their class names with "C" (I just use capital first letter for classes and nothing else).

I've seen these cases a lot myself in the past. I think the book, Clean Code, suggests not doing this though. I can't remember if I really read that a year ago when I read the book, or even why that'd be the case. Personally, a prefix sounds like a good way to go as, you like said, explicitly establishes scope for that variable throughout the class. I could even have local variables with the same name, but no prefix, and the code wouldn't look as confusing, provided the situation would require a variable named like that. I'm a big fan of just the preceding underscore, which appears to be a common convention in Obj-C, C#, etc. I've been told by a lead developer in the past that a preceding underscore isn't a good practice in C++ while it is in C#.

 

@Gooey: The code compiled fine, but it does look misleading. I didn't even think about this as I was still thinking in terms of passing-by-value. Trying to go with references over value when I can do that. but you also bring up a good point. A const reference makes it misleading.

@Strewya: Good point on the code convention. Removing the const reference sounds like the better way to go.

 

EDIT: I found this post on why a we shouldn't start variable names with an underscore. Bottom line: variables starting with an underscore is reserved for C++ implementors. Sounds like this goes for variables starting with both upper and lower-case letters after the underscore. Josh Petrie also pointed out in a previous post I started 2 days ago when I was doing that with my header guards. I thought he only meant that for header guards though. After reading that post, it makes all sense now. I guess m_* might be the way to go. This posts suggests using trailing underscores for naming. I kind of like that naming convention.


In Topic: [Qt 5] Moving Rows For Scene Tree (QAbstractItemModel)

18 August 2015 - 04:55 PM


Actually that does not seem to be entirely accurate, I think removeRows should remove (delete scene nodes), such that you can use it for that specific purpose, say in a context menu or when pressing the delete key while a node is selected. What I found is that you should enable the DragDropOverrideMode on the view for it not to call remove on the original indices, DragDrop vs InternalMove seems not to make a difference to me.

I override removeRows() to specifically remove scene nodes from my own hierarchy's structure between beginRemoveRows() and endRemoveRows(). It's been a few weeks since I worked on this, but I think removeRows() was being called internally by the base class, QAbstractItemModel, whenever I moved something in my hierarchy. Changing drag and drop operations from InternalMove to DragDrop seemed to stop calling removeRows() internally.

 

I agree with trying to avoid having to subclass QTreeView for simple operations. You'd think it'd provide more intuitive signals for when selections change and occur, but hey, there are reimplementation methods for that (ugh)! Again, I'm still pretty new to Qt, and it looks like it doesn't directly handle selections, but rather the QSelectionModel field it has does this.

 

Either way, my hierarchy system appears to work like a charm now. There are only minor changes here, but here's the working source code I've got:

#include "SceneModel.h"
#include "Scene.h"
#include "Node.h"

#include <stdio.h>
#include <stdlib.h>

#include <QKeyEvent>
#include <QMimeData>
#include <QByteArray>
#include <QColor>
#include <QDebug>


Qt::ItemFlags SceneModel::flags(const QModelIndex &index) const
{
	Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
	if(index.isValid())
		return defaultFlags | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsEditable;
	else
		return defaultFlags | Qt::ItemIsDropEnabled;

	return QAbstractItemModel::flags(index);
}

Qt::DropActions SceneModel::supportedDropActions() const
{
	return QAbstractItemModel::supportedDropActions() | Qt::MoveAction;
}

int SceneModel::rowCount(const QModelIndex &parent) const
{
	// make sure the scene's root node is valid and the parent's column is zero
	if(!scene || !scene->GetRoot() || parent.column() > 0)
		return 0;

	// return the parent's children, if valid
	if(parent.isValid())
		return static_cast<Node*>(parent.internalPointer())->GetNumChildren();

	// otherwise, return the children
	return scene->GetRoot()->GetNumChildren();
}

int SceneModel::columnCount(const QModelIndex&) const
{
	return 1;
}

QModelIndex SceneModel::index(int row, int column, const QModelIndex &parent) const
{
	// make sure the root, row and column data is valid
	if(!scene || !scene->GetRoot() || !hasIndex(row, column, parent))
		return QModelIndex();

	// get the parent Node from the index
	Node *parentItem = scene->GetRoot();
	if(parent.isValid())
		parentItem = static_cast<Node*>(parent.internalPointer());

	Node *childItem = parentItem->GetChild(row);
	if(childItem)
		return createIndex(row, column, childItem);

	return QModelIndex();
}

QModelIndex SceneModel::parent(const QModelIndex &index) const
{
	// make sure the index is valid
	if(!scene || !index.isValid())
		return QModelIndex();

	// get the child item to get the parent item
	Node *childItem = static_cast<Node*>(index.internalPointer());
	if(!childItem) return QModelIndex();
	Node *parentItem = childItem->GetParent();

	// return nothing if the parent item is the root
	if(!parentItem || parentItem == scene->GetRoot())
		return QModelIndex();

	return createIndex(parentItem->GetChildIndex(), 0, parentItem);
}

QVariant SceneModel::data(const QModelIndex &index, int role) const
{
	if(!index.isValid())
		return QVariant();

	if(role == Qt::ForegroundRole)
	{
		Node *node = static_cast<Node*>(index.internalPointer());
		if(node && !node->IsActive())
			return QColor(Qt::gray);
		return QVariant();
	}

	// handle displaying the data
	if(role == Qt::DisplayRole || role == Qt::EditRole)
	{
		// get the child and parent Node pointers
		Node *node = static_cast<Node*>(index.internalPointer());
		return QString(node->GetName().c_str());
	}

	return QVariant();
}

bool SceneModel::submit()
{
	// make sure the scene is available
	if(scene)
	{
		// check if the selected node is valid
		if(selectedNode)
		{
			// get the selected node's index, and update its display data
			QModelIndex selectedIndex = createIndex(selectedNode->GetChildIndex(), 0, selectedNode);
			setData(selectedIndex, QString(selectedNode->GetName().c_str()), Qt::DisplayRole);
		}
	}
	return QAbstractItemModel::submit();
}

bool SceneModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
	// check if the index is valid, and either in EditRole or DisplayRole mode
	if(index.isValid() && (role == Qt::EditRole || role == Qt::DisplayRole))
	{
		Node *node = static_cast<Node*>(index.internalPointer());
		if(!node) return false;

		// set the node's new name, and signal that data has changed
		node->SetName(std::string(value.toString().toUtf8()));
		emit dataChanged(index, index);

		// emit signal that specifically signifies that the name has changed
		if(role == Qt::EditRole)
			emit SelectedNodeNameUpdated(node->GetName());
		return true;
	}
	return QAbstractItemModel::setData(index, value, role);
}

bool SceneModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int, const QModelIndex &parent)
{
	// make sure this is aciton shouldn't be ignored
	if(!scene || !data || action == Qt::IgnoreAction)
		return false;

	// get the encoded data of our Node pointer
	QByteArray encodedData = data->data(SceneModelMIMEType);
	Node *node = (Node*)encodedData.toULongLong();
	if(!node) return false;

	// get the parent node
	QModelIndex destinationParentIndex;
	Node *parentNode = static_cast<Node*>(parent.internalPointer());
	if(parentNode)
	{
		destinationParentIndex = parent;
	} else {
		parentNode = scene->GetRoot();
		destinationParentIndex = createIndex(0, 1, scene->GetRoot());
	}

	// get rowCount if row is invalid (this might be an old Qt bug)
	if(row == -1)
		row = parentNode->GetNumChildren();

	// move the row
	Node *sourceParentNode = node->GetParent();
	QModelIndex sourceParent = createIndex(sourceParentNode->GetChildIndex(), 0, sourceParentNode);
	moveRow(sourceParent, node->GetChildIndex(), destinationParentIndex, row);
	return true;
}

QStringList SceneModel::mimeTypes() const
{
	QStringList types;
	types << SceneModelMIMEType;
	return types;
}

QMimeData *SceneModel::mimeData(const QModelIndexList &indexes) const
{
	// make sure there's exactly 1 index and it's valid
	if(indexes.size() != 1 || !indexes[0].isValid())
		return nullptr;

	// get the Node pointer from the index's internal pointer, convert it to quintptr, then to a UTF string
	quintptr nodeAddress = (quintptr)indexes[0].internalPointer();
	QByteArray encodedData(QString::number(nodeAddress).toUtf8());

	// allocate mimeData, provide it with encodedData, and return it
	QMimeData *mimeData = new QMimeData();
	mimeData->setData(SceneModelMIMEType, encodedData);
	return mimeData;
}

bool SceneModel::insertRows(int row, int count, const QModelIndex &parent)
{
	if(!scene)
		return false;

	Node *parentNode = static_cast<Node*>(parent.internalPointer());
	if(!parentNode)
		parentNode = scene->GetRoot();

	// make sure a parent node was found before continuing
	if(parentNode)
	{
		// begin inserting rows, and add begin adding uniquely-named fields
		beginInsertRows(parent, row, row+count-1);

		// insert nodes
		for(int i=0;i<count;++i)
			parentNode->AddChild("New Node");
		endInsertRows();
		return true;
	}
	return QAbstractItemModel::insertRows(row, count, parent);
}

bool SceneModel::removeRows(int row, int count, const QModelIndex &parent)
{
	if(!scene)
		return false;

	Node *parentItem = static_cast<Node*>(parent.internalPointer());
	if(!parentItem)
		parentItem = scene->GetRoot();

	// make sure a parent node was found before continuing
	if(parentItem)
	{
		beginRemoveRows(parent, row, row+count-1);
		parentItem->RemoveChildren(row, count);
		endRemoveRows();
		return true;
	}
	return QAbstractItemModel::removeRows(row, count, parent);
}

bool SceneModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
{
	Node *sourceParentNode = static_cast<Node*>(sourceParent.internalPointer());
	Node *destinationParentNode = static_cast<Node*>(destinationParent.internalPointer());
	Node *childNode = sourceParentNode->GetChild(sourceRow);

	// make sure the child isn't one of the parent nodes
	if(childNode == sourceParentNode || childNode == destinationParentNode)
		return false;

	// if source and destination parents are the same, move elements locally
	if(sourceParentNode == destinationParentNode)
	{
		// only process if a local move is possible
		if(sourceParentNode->IsMovePossible(sourceRow, count, destinationChild))
		{
			beginMoveRows(sourceParent, sourceRow, sourceRow+count-1, destinationParent, destinationChild);
			sourceParentNode->MoveChildren(sourceRow, count, destinationChild);
			endMoveRows();
			return true;
		}
	} else {
		// otherwise, move the node under the parent
		beginMoveRows(sourceParent, sourceRow, sourceRow+count-1, destinationParent, destinationChild);
		childNode->SetParent(destinationParentNode, destinationChild);
		endMoveRows();
		return true;
	}
	return false;
}

Are you trying to achieve a typical hierarchy-based scene graph with a tree view as well? Also, were you able to get it to work without the mandatory MIME data nonsense? I can see where MIME data is necessary when dragging files from say, Explorer or Finder into your view or items between separate views, but not from within the same view.


PARTNERS