Advertisement

How to deal with adding and destroying game objects in UDP?

Started by July 03, 2013 10:17 AM
9 comments, last by wodinoneeye 11 years, 4 months ago

Hey,

I'm using ENet library and it ensures that even packets sent with UDP protocol will be sequenced and packets with lower numbers than those currently received, will be discarded. So it ensures proper order of packets but as we know UDP is still not reliable.

And here's my problem. How to design the communication between peers and hoster to ensure that the info about game objects, which have just been created or destroyed, will be always delivered?

Enet allows for some messages to be reliable. So you can make the important messages reliable.

Advertisement

hmm, yeah it's true but here comes another problem tongue.png. I store all game objects in one std::vector on hoster and when sending data from hoster to player, the thing which identifies which game object this packet belongs to, is the index of the vector. Therefore, after deleting 1 object on hoster the vector is reduced and so indexes change. What if the reliable packet containing information about deleting an object arrived to the client later than UDP packets which would already contain information with reduced vector indexes? The data would go to inappropriate objects until client will get the message about deleting the mentioned object.

You can store an ID in the player object. Use reliable packets to notify player what objects he need to create or destroy.

Use an std::list or std::map as a container for your player objects. Forget about using indices as IDs.

hmm, yeah it's true but here comes once again another problem :P. How to attach ID to objects? In some MMO games which are based on UDP (DC Universe for example) server works all the time. That means that there are a lot of objects created and destroyed during server's lifetime. If they increase ID of new objects by 1 all the time, the ID would eventually exceed the maximum value and would cause server crash I guess. My game is based on matches so it is less likely to happen but I'm curious how ID should be attached in a MMO such as DC Universe?

In some MMO games which are based on UDP (DC Universe for example) server works all the time. That means that there are a lot of objects created and destroyed during server's lifetime. If they increase ID of new objects by 1 all the time, the ID would eventually exceed the maximum value and would cause server crash I guess.

First, the server will not crash (not necessarily, anyway) if you exceed the maximum value. What will happen is that the number will wrap around (overflow).

That, by itself, does not do anything special (not on any real, existing computer, anyway -- in theory there could exist CPUs that generate a trap on overflow). Of course you do have the problem that you will "reuse" old IDs, but that is no problem if they have been destroyed already. Running against physical memory limits (considering 4 billion game objects with a 32-bit ID!) is much more likely to happen, and much sooner.

You do need to make sure that you remember which indices have been deleted, if you want to be safe (in the easiest case, by remembering the lowest valid index, otherwise via a freelist or similar).

Quite likely, relying onto "luck" will work just fine, too (because you just don't create that many objects that fast), but I'd recommend doing it properly anyway. Relying on luck is rarely a good idea, if something can go wrong, it will eventually go wrong too (maybe in a few years when nobody remembers why).

Depending on what representation you use for your ID, overflow happens considerably sooner or later. Assuming your server creates a 10,000 new objects per second (which is an awful lot!), a 16-bit ID will overflow in 6 1/2 seconds, and a 32-bit ID will overflow after 23 hours, 18 minutes. A 64-bit ID will not overflow during your lifetime, or during the likely remaining existence of mankind on this planet. That is, unless you deem it reasonable to expect that we haven't had World War III, or otherwise completely destroyed the planet in 58 million years from now, and that your computer program is still running by then.

With that in mind, if you can't be bothered to keep track of used and freed IDs, use a 64-bit ID (or use a 48-bit ID and assume that the the server will reboot at least once every 800 years!). You'll never have to worry. Of course it eats up a few extra bytes of storage, but since most IDs that appear together will be very similar except in the last few bits, you can compress those away.

Advertisement

Thanks a lot for answer! I thought about using 64 bit ID but didn't expect it to survive for so long :D. It will work for me then I guess :P. Thanks for the info again.

Well, I have another question related to IDs. If all objects are identified by their IDs (not by indices of the container they are in) how could I quickly find the appropriate object in order to ascribe data to him? Is there no other way than iterating through all objects until I find the one with right ID?

Using a map (as in C++ std::map) would be one easy and obvious way of reducing "must iterate every element" to "must only touch log2(n) elements".

Another easy way (a hack, but nevertheless) would be to use the object's memory address as ID. There's nothing to look up and nothing to search, and the ID is guaranteed to be unique.

Just make sure you don't free and immediately reuse an address (as it's the case with most allocators) and then assume that the client "magically" knows that this is now a different object. But hey, hacks almost always come with a little "gotcha".

One solution to this issue is, by the way, the same as you use in lockfree concurrent algorithms to combat the ABA problem: Exploit the fact that objects are aligned (most of the time to 8 or so bytes) so the lowest few bits are all zero and you can put a counter into these.

This idea about using object's memory address is just brilliant! Though I'm afraid I'm still not pro enough to implement it tongue.png but I would like to find out sth more about this if you don't mind ^^.

I'm not sure how could it work? The server would create an object on its side and would send it to client together with its memory address? Then client would have to create the object in the same memory location, as it had been created on the server, using sth like http://www.parashift.com/c++-faq-lite/placement-new.html? But do I have certainty that the memory address which was free on the server will be free on the client as well? Or maybe I'm completely wrong with my assumptions?tongue.png

This idea about using object's memory address is just brilliant!


That will be different between the server and each of the clients, so the clients would still need a look-up dictionary if you used the server's memory address. That may still be OK. Personally, I'd just use an incrementing 32-bit integer. If I create 100 objects per second, that will still let me go 40,000,000 seconds before wrapping. That's almost 463 days, or over fifteen months.

Btw: The "go-to" container for "bags of stuff I need to find again" is a hash table. In C++, it's called the std::unordered_map<>. It is O(1) for insert, find, and delete. However, if you iterate over it, items will come out in some undetermined order; not nicely sorted as in std::map<>. If you don't yet have a C++11 standard library, you probably have this container as std::tr1::unordered_map<> instead.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement