Archive for March, 2008

Graphical game editor

Monday, March 31st, 2008

Imagine a game engine where you can make games without a single line of code. Instead, you design and create through a GUI by dragging, dropping, setting properties for, and linking modules. Each module represents some user-level component of the game world. By user-level I mean something a game designer would refer to, rather than what a programmer would refer to. So you would have a skybox module, a level module, an enemy module, and so on. Each module is tweaked through a property list, such as what the skybox looks like, what model the enemy should use, and so on. One way to think of it is as a game design engine, that happens to also create the game that you are designing.

As the value of the editor comes from the editing tools, and the interpretation of modules, it is not necessary to write libraries such as a graphics engine or a sound engine. Instead, one could take free or low-cost best-in-class libraries and put them together to power your editor. This will massively reduce the programming cost and time, while delivering the same value.

What makes this possible is that most of the programming in a game solves the same fundamental problems. Characters walk around, guns shoot in the direction they are pointed, enemies die and disappear, triggers react on proximity or speech interactions, and so on. So the key to successfully programming this is to define how low level to solve the problem. Too low level and people will complain they are just programming in a GUI (See Mental Mill). Too high level and people will complain you can only make one type of game. The approach I would take is the same as I do with RakNet: Be high level and just do a good enough job of self-managing that people don’t care about the low level. Focus on one type of game (shooter, RTS), do a good job with that genre, then add modules for other genres as time goes on.

I think this could be programmed by a team of two people over one year to reach beta. Another year for release where you could actually make interesting games. And another year for the product to really be polished, where people won’t complain too much in the forums.

It could be monetized by running the game in a web-browser only, only allowing registered users to make games, and showing ads for unregistered users.

If I ever hit it big with RakNet this would probably be the next project I do.

Additional RakString optimizations

Sunday, March 30th, 2008

I removed a couple of conditions, inlined and unrolled a function, and replaced the generic memory pool with a stack of pointers.

RakString vs. std::string
Creation: RakString roughly fixed cost. std::string improves over time. Depending on when the test occurs, RakString ranges from 3X faster to roughly equal.
Remove at head (shifting a large list): RakString is consistently 4.25X faster
Deletion: Both are fast enough to not register one millisecond.

5000 iterations
Reference is new / delete and strcpy with a straight char*

Insertion 1 Ref=5 Rak=13, Std=48
RemoveHead Ref=18 Rak=150, Std=674
Insertion 2 Ref=5 Rak=6, Std=13
RemoveTail Ref=0 Rak=0, Std=0
Insertion 1 Ref=6 Rak=7, Std=5
RemoveHead Ref=30 Rak=153, Std=673
Insertion 2 Ref=8 Rak=6, Std=5
RemoveTail Ref=0 Rak=0, Std=0
Insertion 1 Ref=6 Rak=7, Std=8
RemoveHead Ref=62 Rak=151, Std=666
Insertion 2 Ref=7 Rak=7, Std=8
RemoveTail Ref=0 Rak=0, Std=0
Insertion 1 Ref=6 Rak=7, Std=13
RemoveHead Ref=61 Rak=156, Std=669
Insertion 2 Ref=11 Rak=7, Std=12
RemoveTail Ref=0 Rak=0, Std=0

10000 iterations

Insertion 1 Ref=13 Rak=26, Std=141
RemoveHead Ref=58 Rak=1167, Std=3425
Insertion 2 Ref=12 Rak=13, Std=7
RemoveTail Ref=0 Rak=0, Std=0
Insertion 1 Ref=13 Rak=13, Std=30
RemoveHead Ref=108 Rak=1122, Std=3334
Insertion 2 Ref=12 Rak=14, Std=29
RemoveTail Ref=0 Rak=0, Std=0
Insertion 1 Ref=10 Rak=14, Std=22
RemoveHead Ref=878 Rak=1116, Std=3363
Insertion 2 Ref=12 Rak=13, Std=23
RemoveTail Ref=1 Rak=0, Std=0
Insertion 1 Ref=13 Rak=13, Std=14
RemoveHead Ref=886 Rak=1128, Std=3349
Insertion 2 Ref=14 Rak=14, Std=14
RemoveTail Ref=0 Rak=0, Std=0

Summary

Reference is much faster in shifting large lists because it is just copying a single variable internally. Neither std::string nor RakString know that contextually they don’t need to do intermediate copies, so there is more overhead and thus they are slower. However, RakString uses a reference counted pointer, so the shifting is limited to a few variables vs. std::string which actually does a full copy. RakString is about 20% slower than reference, but std::string is over 350% slower.

Reference is much faster the first iteration, because RakString has to create the memory pools. Presumably std::string does this as well. After the first iteration, RakString varies from equally fast to 50% slower. std::string varies from equally fast to 250% slower. However, oddly enough in one test std::string was faster than both – this was probably a fluke due to thread context switching.

While it doesn’t show in these numbers, in some later tests reference got slower the more tests I ran, until it was actually far slower than both RakString and std::string. This was probably due to memory fragmentation.

If you are going to do a lot of shifting around in lists, just use char* that you allocated yourself. Otherwise, use RakString. RakString is faster than std::string, and doesn’t cause linker errors and export warnings when linking as a DLL, a big plus. The constructor and Set function is varidic. It complies with camelCase conventions. And as I use it more I can add inherent functionality that std::string is missing, such as splitpath.

Here’s the code. It’s stand-alone except for DS_List, and SimpleMutex. For the export and assertions you can just replace those with your own versions. SimpleMutex could be removed if this was only used in a single thread at a single time.

Header
Source

Pretty fast string class

Sunday, March 30th, 2008

I wrote my own string class, that is 5X faster than std::string for creation and 2X faster for copying. However, it is slower if I create a string that was previously deleted. I’m not sure what they are doing, but in that case creation is 2X faster than mine.

It would be really cool if I could get it faster in all cases.

Here’s the code:
RakString.cpp
RakString.h

Times are in milliseconds
Ref is just inserting a number, to show the speed of the list itself.
Rak is my string
Std is the std::string

Insertion 1 Ref=0 Rak=25, Std=102
RemoveHead Ref=106 Rak=4964, Std=8577
Insertion 2 Ref=1 Rak=22, Std=12
RemoveTail Ref=0 Rak=0, Std=0

Insertion 1 Ref=1 Rak=24, Std=102
RemoveHead Ref=107 Rak=4868, Std=8585
Insertion 2 Ref=0 Rak=23, Std=13
RemoveTail Ref=0 Rak=0, Std=0

Insertion 1 Ref=1 Rak=25, Std=104
RemoveHead Ref=107 Rak=4879, Std=8553
Insertion 2 Ref=0 Rak=22, Std=13
RemoveTail Ref=0 Rak=0, Std=0

Test code

static const int repeatCount=15000;
DataStructures::List rakStringList;
DataStructures::List stdStringList;
DataStructures::List referenceStringList;
unsigned i;
RakNetTime beforeReferenceList, beforeRakString, beforeStdString, afterStdString;

beforeReferenceList=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	referenceStringList.Insert(0);
beforeRakString=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	rakStringList.Insert("Aalsdkj alsdjf laksdjf ;lasdfj ;lasjfd");
beforeStdString=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	stdStringList.Insert("Aalsdkj alsdjf laksdjf ;lasdfj ;lasjfd");
afterStdString=RakNet::GetTime();
printf("Insertion 1 Ref=%i Rak=%i, Std=%i\n", 
beforeRakString-beforeReferenceList, 
beforeStdString-beforeRakString,
 afterStdString-beforeStdString);

beforeReferenceList=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	referenceStringList.RemoveAtIndex(0);
beforeRakString=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	rakStringList.RemoveAtIndex(0);
beforeStdString=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	stdStringList.RemoveAtIndex(0);
afterStdString=RakNet::GetTime();
printf("RemoveHead Ref=%i Rak=%i, Std=%i\n",
 beforeRakString-beforeReferenceList,
 beforeStdString-beforeRakString,
 afterStdString-beforeStdString);

beforeReferenceList=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	referenceStringList.Insert(0);
beforeRakString=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	rakStringList.Insert("Aalsdkj alsdjf laksdjf ;lasdfj ;lasjfd");
beforeStdString=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	stdStringList.Insert("Aalsdkj alsdjf laksdjf ;lasdfj ;lasjfd");
afterStdString=RakNet::GetTime();
printf("Insertion 2 Ref=%i Rak=%i, Std=%i\n",
 beforeRakString-beforeReferenceList,
 beforeStdString-beforeRakString,
 afterStdString-beforeStdString);

beforeReferenceList=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	referenceStringList.RemoveAtIndex(referenceStringList.Size()-1);
beforeRakString=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	rakStringList.RemoveAtIndex(rakStringList.Size()-1);
beforeStdString=RakNet::GetTime();
for (i=0; i < repeatCount; i++)
	stdStringList.RemoveAtIndex(stdStringList.Size()-1);
afterStdString=RakNet::GetTime();
printf("RemoveTail Ref=%i Rak=%i, Std=%i\n",
 beforeRakString-beforeReferenceList, 
beforeStdString-beforeRakString, 
afterStdString-beforeStdString);

Need a free cross-platform moddable GUI system

Thursday, March 27th, 2008

I need to add a moddable GUI system to RakNet so that users using the lobby don’t have to implement the 40 or so client features themselves. The problem is that this is such a huge task when you think about it. I would need a cross-platform graphics system that can work at the same time as the user’s game. Then there needs to be a GUI system on top of that, and not just windows but all the little effects too. Effects usually include sound, so that’s another issue (at least I can use FMOD there). Of course, to interact with the GUI system it also needs an input system, and the input system has to be either stand-alone or work with the end-users own input system.

There’s some free GUI systems out there (wxWidgets among others) but none of them support consoles as far as I know.

There’s Scaleform but I don’t want to learn flash and it’s not free. Still, that’s probably the best compromise I’m going to find. It’s certainly easier than trying to roll my own complete solution.

Rent a coder is pretty neat

Tuesday, March 25th, 2008

The first time I heard about RentACoder was from David Byttow. He was telling me how he wanted to make a new account and get all 10’s for every project. It’s a service where post a bid with a detailed description of what you want done, and people bid on it. In one week I got about 10 bids on something I posted. It was pretty easy to setup and use. They have a review system so people want to do a good job because if you get a lot of bad reviews nobody wants to hire you anymore.

The only problem I have with it is the same problem an art gallery would have if they posted a bid on a street corner for new paintings. There’s a 100X difference between the best and worst programmers. If I wasn’t a programmer I would only care that the job gets done. Since I am one, I also care that the job gets done at a certain level of quality that goes far beyond the code just working. The code has to be documented, adhere to existing standards, be a good solution architecturally, solve the problem completely, be fast, do not leak memory, and solve the intended problem. A lot of this comes from investment in the code, which is hard to get from a hit and run contractor.

75% of the bids I got are clearly unqualfied. They have an arbitration system if things go bad, but I don’t want to deal with that just over a few hundred dollars, I just want the job to get done right. Also, arbitration does not lend itself to quality, since the things I posted are somewhat objective and require thought. A bad job that satisfies the minimum requirements may pass arbitration, but be totally useless to me.

I was thinking one good way to use this system is for annoying bug fixes. For example:

My email sender class:
Header
Source

Does not work with Gmail POP

The error returned is

220 mx.google.com ESMTP f42sm3090097rvb.13
250 mx.google.com at your service
530 5.7.0 Must issue a STARTTLS command first f42sm3090097rvb.13

It’s a good thing to post because it either works or not. There isn’t much in the way of objective metrics, and higher level concepts like architecture aren’t really an issue. It’s worth a hundred bucks to me to pass it off, since I hate dealing with RFCs. (Not that I hate standards, just that the RFCs are sometimes ambiguous).

Good service to use if you don’t care about code quality and time is not an issue.

Autoserialized, hook free, RPC

Saturday, March 22nd, 2008

Traditional RPC requires several steps to setup and receive the call. First, you have to serialize the input parameters. Second, you send the serialized data, along with an identifer for what function to call. Third, you deserialize the data, and put it in some known format. Lastly, you call the function, with that format as the parameter.

For example, to call this function on a remote system:

void PrintNum(int i)
{
printf(“%i\n”, i);
}

I would need 3 functions:

Old way: Serialize data

void SerializeNum(int i, BitStream *bs)
{
bs->Write(i);
}

Old way: Call with serialized data

void SendRPC(BitStream *serializedData)
{
CALL(“DeserializeAndCallPrintNum”, serializedData, …);
}

Old way: Deserialize and call real function

void DeserializeAndCallPrintNum(BitStream *bs)
{
int i;
bs->Read(i);
PrintNum(i);
}

I came up with a way to get rid of Serialize and DeserializeAndCallPrintNum(). To the sender, the RPC is essentially varidic, in that the receiver can have an unknown and arbitrary number of parameters.

New way: Remote function call, passing parameter list directly

CALL(“PrintNum”, 5);

It’s less efficient, but much easier to use. The key gain from this is that no hooks are necessary. When a function is called, it can trigger itself on remote systems. Furthermore, I can inject extra parameters into the parameter list so that I know if the function was called from the network or not.

New way: Self-triggering networked function

PrintNum(10, false);

void PrintNum(int i, bool calledFromNetwork)
{
if (calledFromNetwork==false)
CALL(“PrintNum”, i); // Over the network, last parameter is injected by network code with true
printf(“%i\n”, i);
}

The process to do this is:

1. Use templates to serialize the input parameters, such that I can tell the number and size of each parameter.
2. Send the parameters across the network, along with the function name
3. Lookup the previously stored function pointer. Deserialize the parameters, expanding parameters under the size of a register to the size of a register (4 bytes).
4. Push parameters on the stack
5. Call the function

Here’s proof-of-concept code:
Autoserialized, hook free, RPC proof of concept

Drawbacks:

  1. Only works with parameters that are actually copied correctly into the stack (shallow copy, no arrays)
  2. No pointers (old system didn’t have this either)
  3. Lose flexibility to do algorithmic encoding, such as if (hasPosition) Write(position)

I might be able to fix the first point. If I can somehow detect what type of template parameter was passed maybe I can query if the object fulfills some kind of interface. The biggest drawback to the first point is that you can’t pass strings or arrays of non-fixed length. So you either have to be very wasteful, or fallback to the old system.

Middleware Marketing 5: Quality control

Thursday, March 20th, 2008

In the game industry if a game ships badly and doesn’t sell in the first two weeks, it’s all over. The only game that has ever recovered from a bad release that I know of is Anarchy Online and that was only because it came out early enough when massively multiplayer games were still novel. But that is the exception, not the rule. How many games have you played that sucked, and you tried them again a year later just to see if a patch made them better?

Giving a bad impression in the middleware market is not quite such a death-sentence, because people expect middleware to improve and change over time. However, a bugged release is still going to cause you pain for a long time.

Negative posts that are no longer true

When I search for “RakNet vs. X” where X is one of my competitors, I sometimes see posts “RakNet is good, but X does this better.” Three years ago when the post was written, that was true, but people reading up on RakNet wouldn’t know things have changed since then. Even my Gamedev review is based on like 1.0 of RakNet. It’s positive (thank god!) but it’s totally inapplicable to the current release. Imagine if it had been negative, all things currently equal?

Spawn competition

The original reason I wrote RakNet was because I was disenchanted with HawkNL. I respect what the author has done, but I essentially put him out of business if the forum is anything to go by. Usually I won’t link to my competitors so I don’t improve their Google rank, but HawkNL is so long gone I don’t even care. Arguably, I even put Microsoft’s DirectPlay out of business, as they shut down a few years after RakNet was created as well. Mabye not THE reason, but perhaps the straw that broke the camel’s back.

Old releases scattered around

Sometimes people take it upon themselves to write wrappers for RakNet, or mirror it. If the wrapper is just for a release that lacks features, that is OK. But if the release has bugs, then so does the wrapper, and unlike your own releases, the wrapper is out of your control and keeps on giving you a bad name.

Disgruntled users that won’t come back anytime soon

The first graphics library I ever used, before I even graduated college, was Power Render. The documentation, support, and distribution wasn’t very good, and I paid like $250 for it, which was a LOT of money for me at the time. I’m sure it’s much better now after 8 years or so. But even now, if I’m going to look for a graphics library, Power Render is going to be last on the list because I don’t know if I like the other libraries or not, but I know I once had a bad experience with Power Render. If Power Render has just lacked features and otherwise worked, I would have missed those features, but wouldn’t have been mad or felt like I wasted money.

Key point

My biggest mistake ever regarding bad releases was when adding flow control. Flow control is tremendously hard to get working right, unless you’ve done it before it’s hard to understand just how difficult and error prone it is. Bad flow control causes lag and spikes, and because this issue was so hard to get working right under every internet condition, every type of game, every operating system for about 2 years I got occasional complaints about these kinds of issues. In my opinion this is the reason I have any low-cost competitors at all. Disgruntled users that had bad flow control under some case that went to my 3rd place competitor (which doesn’t have flow control, and is otherwise 4 years behind me in development) and had their issues fixed. It’s better not to have a feature at all than one that doesn’t work right in all cases and is well-tested.

So the key point is, be very careful about testing, and don’t release with bugs.

Middleware Marketing 4: Connections and clout

Wednesday, March 19th, 2008

Last week when I was looking for web design companies to make a new website for RakNet, I sent out about 10 requests for bids. The request was well-written, complete, and professional. However, I only got 5 responses. At roughly the same time, I contacted 3 marketing firms to help market RakNet, and didn’t get any responses.

Imagine if, instead of Jenkins Software, Microsoft or Sony sent that same sales request? How many replies would they have gotten?

This is clout, and its based on how much money the other side thinks you have, and how much they think they can gain from dealing with you. It’s like a game almost, where you have to have a certain clout level to talk to certain NPCs.

Connections are the cheat codes to give yourself clout with certain NPCs.

Yesterday I said to a friend, “Friends and connections are what this industry is about I think.” In fact some business activities require so much clout that it is nearly impossible to achieve them without connections, or the clout that connections have already provided you:

1. Become a licensed console developer
2. Sell to a company that makes over a few million in sales a year
3. Purchase major advertising
4. Get mentioned in major game industry news reports
5. Hire top-tier talent
6. Partner with other companies to form bundling deals
7. Get outside funding

And if I wrote a list of the 7 activities that make a middleware company most likely to succeed, what would that be?

Succeeding with connections is not a matter of making false friends of hope of getting something in return. It’s about being able to work with people you trust, and having those people trust you in return. Big deals require trust and 90% of our daily interactions is based the human factor.

Don’t burn your bridges, make an effort to know people, give good customer support, help people who ask for help sincerely, and keep in contact with the people you do know.

Middleware Marketing 3: Compete on quality, not price

Monday, March 17th, 2008

When I originally released RakNet I charged $2,000 per application. Later, I charged $0 (free) to increase market share because nobody was buying it at $2,000. Later, as it improved I charged $4,000. I got some sales there, so raised my price to $5,000. All the while I was wondering at the greed of my competitors that charged hundreds of thousands for more restrictive licensing terms. I was competing on price because I could afford to do so, and while my prices were good for an individual at the time, they weren’t enough to hire web designers, support, testers, etc. so I was endlessly stuck as a one man operation. I even read back then on Joel on Software about startups “Don’t compete on price” but didn’t listen.

What I didn’t understand at the time is while there was a HUGE difference between $2000 and $10,000 to me as an individual, to a developer that is the SAME AMOUNT OF MONEY – if your software helps them, they will license it either way, and it’s just as hard to sell to them. This is even more true with a publisher. You could charge basically what it would cost to write, and they would still license your software as long as it saves them time.

The hard part is:

1. Get them to know about your product in the first place
2. Convince them your product is worth using
3. Get through the non-programmers and legal to actually get money into your bank account

Because I undercharged originally, I short-changed my company money licensees would have been willing to pay. And since I was charging one-man prices, I never grew beyond a one man operation.

You might ask, “So what, as long as the quality is the same?” I did, and for programmers it doesn’t matter, because they are looking at your code, not your website. This will get some sales at smaller companies. At larger companies, programmers usually aren’t the ones writing the checks. Before signing any big deal, HR, lawyers, and producers are going to look at your site. And as I found out the hard way, all it takes is for a lawyer to say “This guy’s site sucks, so we don’t even want to deal with him” to stop a deal.

Also, by not financially growing your company, your competitors that do get the funds / clout to do direct marketing, and bundling, and get their software on the front page of gaming sites. I on the other hand have to sell to smaller customer base, many of whom already have my competitor’s product through a bundle. It’s like how I bought Windows Vista with my laptop, something I would have never done if I had the choice. Right now I have so little clout that even some advertisers won’t return my calls.

Because the quality is so good, RakNet actually has #2 marketshare right now, and I’m proud of that. But I only recently started charging realistic prices, and have to play catch-up.

Middleware marketing 2: Require links and logos

Monday, March 17th, 2008

Bit of advice based on what I’m going through now:

If you ever write middleware, make sure your contract includes that you can list the customer on the customer page of your website. It’s easy to get when selling your middleware, but quite hard to get retroactively. Right now a huge game is coming out using RakNet, and I’m not sure if I can list them as a customer which is very frustrating.

I hear that all other middleware companies also force the developers to list the middleware library logos on their splash screen. That’s another thing I previously had optional, and in practice has only happened once that I’m aware of. I’ve probably cheated myself out of a lot of free marketing and sales because of that. Make it another requirement.

I have another deal in the works and just delayed it to ask for this, but it’s important enough I’d rather lose the deal than get a deal without it.